diff --git a/README.md b/README.md index fd7b230c..4b1ce9ab 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ChestnutCMS v1.5.5 +# ChestnutCMS v1.5.6 ### 系统简介 @@ -14,33 +14,34 @@ ChestnutCMS是前后端分离的企业级内容管理系统。项目基于[RuoYi 资讯站演示地址:(会员演示账号:xxx333@126.com / a123456) -图片站演示地址:PC端: 移动端: +图片站演示地址:PC端: 移动端: -游戏站演示地址:PC端: 移动端: +游戏站演示地址:PC端: 移动端: -影视站演示地址:PC端: 移动端: +影视站演示地址:PC端: 移动端: + +更多演示站访问: ### 开发环境 - 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交流群 diff --git a/chestnut-admin/Dockerfile b/chestnut-admin/Dockerfile index 44682b03..935b144e 100644 --- a/chestnut-admin/Dockerfile +++ b/chestnut-admin/Dockerfile @@ -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"] \ No newline at end of file +ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher"] \ No newline at end of file diff --git a/chestnut-admin/pom.xml b/chestnut-admin/pom.xml index 05da9247..1bf250a7 100644 --- a/chestnut-admin/pom.xml +++ b/chestnut-admin/pom.xml @@ -3,7 +3,7 @@ chestnut com.chestnut - 1.5.5 + 1.5.6 4.0.0 jar @@ -165,6 +165,9 @@ + + true + diff --git a/chestnut-admin/src/main/resources/application-dev.yml b/chestnut-admin/src/main/resources/application-dev.yml index f7a52c77..7448cfc2 100644 --- a/chestnut-admin/src/main/resources/application-dev.yml +++ b/chestnut-admin/src/main/resources/application-dev.yml @@ -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: diff --git a/chestnut-admin/src/main/resources/application-prod.yml b/chestnut-admin/src/main/resources/application-prod.yml index ddf221c6..a661d84c 100644 --- a/chestnut-admin/src/main/resources/application-prod.yml +++ b/chestnut-admin/src/main/resources/application-prod.yml @@ -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: diff --git a/chestnut-admin/src/main/resources/application-test.yml b/chestnut-admin/src/main/resources/application-test.yml index 1580193b..34f3b008 100644 --- a/chestnut-admin/src/main/resources/application-test.yml +++ b/chestnut-admin/src/main/resources/application-test.yml @@ -5,7 +5,7 @@ chestnut: # 代号 alias: ChestnutCMS # 版本 - version: 1.5.5 + version: 1.5.6 # 版权年份 copyrightYear: 2022-2024 system: diff --git a/chestnut-admin/src/main/resources/db/migration/mysql/V1.5.6__update.sql b/chestnut-admin/src/main/resources/db/migration/mysql/V1.5.6__update.sql new file mode 100644 index 00000000..979bdf4c --- /dev/null +++ b/chestnut-admin/src/main/resources/db/migration/mysql/V1.5.6__update.sql @@ -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 '模板配置'; + diff --git a/chestnut-cms/chestnut-cms-advertisement/pom.xml b/chestnut-cms/chestnut-cms-advertisement/pom.xml index 1302f909..67e12410 100644 --- a/chestnut-cms/chestnut-cms-advertisement/pom.xml +++ b/chestnut-cms/chestnut-cms-advertisement/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-advertisement diff --git a/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/controller/AdSpaceController.java b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/controller/AdSpaceController.java index b5058a8d..d8367371 100644 --- a/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/controller/AdSpaceController.java +++ b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/controller/AdSpaceController.java @@ -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(); diff --git a/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/controller/AdvertisementController.java b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/controller/AdvertisementController.java index 7f04a9c0..587820c6 100644 --- a/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/controller/AdvertisementController.java +++ b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/controller/AdvertisementController.java @@ -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> list = advertisementService.getAdvertisementTypeList().stream() @@ -89,7 +96,11 @@ public class AdvertisementController extends BaseRestController { public R 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) diff --git a/chestnut-cms/chestnut-cms-article/pom.xml b/chestnut-cms/chestnut-cms-article/pom.xml index 9709fc71..f0b8c6b1 100644 --- a/chestnut-cms/chestnut-cms-article/pom.xml +++ b/chestnut-cms/chestnut-cms-article/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-article diff --git a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleContent.java b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleContent.java index 57e7a0ea..6b78b7ec 100644 --- a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleContent.java +++ b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleContent.java @@ -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 { private IArticleService articleService; @@ -48,15 +50,13 @@ public class ArticleContent extends AbstractContent { 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 { 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 { @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()); } diff --git a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleContentType.java b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleContentType.java index 5f127eec..10507192 100644 --- a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleContentType.java +++ b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleContentType.java @@ -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 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 publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(), + List 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 publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(), + List 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; } diff --git a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleCoreDataHandler.java b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleCoreDataHandler.java index c2cb7275..9d0a821b 100644 --- a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleCoreDataHandler.java +++ b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleCoreDataHandler.java @@ -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 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) { diff --git a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleUtils.java b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleUtils.java index 76ce9fa8..5b6b9dd8 100644 --- a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleUtils.java +++ b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/ArticleUtils.java @@ -117,7 +117,7 @@ public class ArticleUtils { * 替换为ssi引用标签 *

*/ - 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)); } } } diff --git a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/IArticleBodyFormat.java b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/IArticleBodyFormat.java index 59d63b4c..b61ca8a7 100644 --- a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/IArticleBodyFormat.java +++ b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/IArticleBodyFormat.java @@ -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); + /** * 文章正文内容发布预览处理 */ diff --git a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/PublishPipeProp_UEditorCss.java b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/PublishPipeProp_UEditorCss.java index de100fb9..ee9f0519 100644 --- a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/PublishPipeProp_UEditorCss.java +++ b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/PublishPipeProp_UEditorCss.java @@ -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; diff --git a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/format/ArticleBodyFormat_RichText.java b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/format/ArticleBodyFormat_RichText.java index ddebc27d..11b36956 100644 --- a/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/format/ArticleBodyFormat_RichText.java +++ b/chestnut-cms/chestnut-cms-article/src/main/java/com/chestnut/article/format/ArticleBodyFormat_RichText.java @@ -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) { // 处理内容扩展模板占位符 diff --git a/chestnut-cms/chestnut-cms-block/pom.xml b/chestnut-cms/chestnut-cms-block/pom.xml index 30655343..550a6595 100644 --- a/chestnut-cms/chestnut-cms-block/pom.xml +++ b/chestnut-cms/chestnut-cms-block/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-block diff --git a/chestnut-cms/chestnut-cms-block/src/main/java/com/chestnut/block/ManualPageWidgetType.java b/chestnut-cms/chestnut-cms-block/src/main/java/com/chestnut/block/ManualPageWidgetType.java index cd4eeb24..fc40a10d 100644 --- a/chestnut-cms/chestnut-cms-block/src/main/java/com/chestnut/block/ManualPageWidgetType.java +++ b/chestnut-cms/chestnut-cms-block/src/main/java/com/chestnut/block/ManualPageWidgetType.java @@ -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; } diff --git a/chestnut-cms/chestnut-cms-comment/pom.xml b/chestnut-cms/chestnut-cms-comment/pom.xml index 2ab1f173..3a753be3 100644 --- a/chestnut-cms/chestnut-cms-comment/pom.xml +++ b/chestnut-cms/chestnut-cms-comment/pom.xml @@ -6,7 +6,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-comment diff --git a/chestnut-cms/chestnut-cms-contentcore/pom.xml b/chestnut-cms/chestnut-cms-contentcore/pom.xml index fac036f1..fb0c285d 100644 --- a/chestnut-cms/chestnut-cms-contentcore/pom.xml +++ b/chestnut-cms/chestnut-cms-contentcore/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-contentcore diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CatalogController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CatalogController.java index 5d9dd797..aa7ab2ab 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CatalogController.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CatalogController.java @@ -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 publishPipeProps = this.publishPipeService.getPublishPipeProps(site.getSiteId(), + List 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 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 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); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/ContentController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/ContentController.java index e0e1d626..87052268 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/ContentController.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/ContentController.java @@ -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 page = q.page(new Page<>(pr.getPageNumber(), pr.getPageSize(), true)); List 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()); } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CoreController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CoreController.java index f782af79..4b03a832 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CoreController.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CoreController.java @@ -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()); } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/PageWidgetController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/PageWidgetController.java index 85c2dd4a..3eca6c70 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/PageWidgetController.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/PageWidgetController.java @@ -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 publishPipes = this.publishPipeService.getPublishPipes(pageWidget.getSiteId()); + List 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); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/PublishPipeController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/PublishPipeController.java index 9784a930..6d165b66 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/PublishPipeController.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/PublishPipeController.java @@ -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 datalist = this.publishPipeService.getPublishPipes(site.getSiteId()) - .stream().map(p -> PublishPipeProp.newInstance(p.getCode(), p.getName(), null)) + List datalist = this.publishPipeService.getPublishPipes(site.getSiteId()) + .stream().map(p -> PublishPipeProps.newInstance(p.getCode(), p.getName(), null)) .collect(Collectors.toList()); return this.bindDataTable(datalist); } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/SiteController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/SiteController.java index 61958e55..5d8ba12c 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/SiteController.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/SiteController.java @@ -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 getDashboardSiteInfo() { + CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest()); + List 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 publishPipeProps = this.publishPipeService.getPublishPipeProps(site.getSiteId(), + List 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 publishPipeProps = this.publishPipeService.getPublishPipeProps(site.getSiteId(), + List publishPipeProps = this.publishPipeService.getPublishPipeProps(site.getSiteId(), PublishPipePropUseType.Site, site.getPublishPipeProps()); dto.setPublishPipeProps(publishPipeProps); return R.ok(dto); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/AbstractContent.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/AbstractContent.java index 61824b7e..0402a65c 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/AbstractContent.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/AbstractContent.java @@ -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 implements IContent { 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 implements IContent { 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 implements IContent { 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 implements IContent { this.getContentService().dao().remove(new LambdaQueryWrapper() .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)); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/AbstractPageWidget.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/AbstractPageWidget.java index c0d00fbe..534de97b 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/AbstractPageWidget.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/AbstractPageWidget.java @@ -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 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; + } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/IContent.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/IContent.java index b2b0c5aa..b40fbb69 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/IContent.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/IContent.java @@ -33,6 +33,8 @@ import java.util.Objects; */ public interface IContent { + String PARAM_IS_DELETE_BY_CATALOG = "isDeleteByCatalog"; + /** * 获取站点ID */ diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/IResourceType.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/IResourceType.java index 1355ca52..626cca8c 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/IResourceType.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/IResourceType.java @@ -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 process(CmsResource resource, File tempFile) throws IOException { + return List.of(); } default void asyncProcess(CmsResource resource) { - } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/InternalURL.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/InternalURL.java index fc9b76bb..57a695cd 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/InternalURL.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/InternalURL.java @@ -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 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 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() { diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/SiteImportContext.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/SiteImportContext.java index 143c237a..1ea8a454 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/SiteImportContext.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/SiteImportContext.java @@ -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)) { diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/impl/InternalDataType_PageWidget.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/impl/InternalDataType_PageWidget.java index 55a4b535..cac5258f 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/impl/InternalDataType_PageWidget.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/impl/InternalDataType_PageWidget.java @@ -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()); } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/impl/ResourceType_Image.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/impl/ResourceType_Image.java index bdf30377..ba0397e7 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/impl/ResourceType_Image.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/impl/ResourceType_Image.java @@ -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 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 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 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 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 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 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 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 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); } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/CmsPageWidget.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/CmsPageWidget.java index 7a94c4c2..2e946cbd 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/CmsPageWidget.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/CmsPageWidget.java @@ -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 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; + } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/CmsResource.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/CmsResource.java index 90868cfb..2ee4c3e9 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/CmsResource.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/CmsResource.java @@ -66,7 +66,9 @@ public class CmsResource extends BaseEntity { /** * 存储类型,默认:local + * @deprecated 1.5.6版本开始不再根据此字段生成资源路径,资源路径统一根据当前站点资源存储配置生成。 */ + @Deprecated(forRemoval = true, since = "1.5.6") private String storageType; /** diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/InitByContent.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/InitByContent.java index 3f9f3176..0a843ef9 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/InitByContent.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/InitByContent.java @@ -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 srcList = content.getImages().stream().map(InternalUrlUtils::getActualPreviewUrl).toList(); + this.setImagesSrc(srcList); + this.setLogoSrc(srcList.get(0)); } } else { this.setImages(List.of()); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/CatalogUpdateDTO.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/CatalogUpdateDTO.java index 1588565d..75555305 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/CatalogUpdateDTO.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/CatalogUpdateDTO.java @@ -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 publishPipeDatas; + private List publishPipeDatas; /* * 自定义参数 diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/ContentDTO.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/ContentDTO.java index efcb0d1e..ccc37107 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/ContentDTO.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/ContentDTO.java @@ -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 publishPipeProps; + private List publishPipeProps; /** * 栏目扩展配置 diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/FileOperateDTO.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/FileOperateDTO.java index 4b3179cf..5d6b9073 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/FileOperateDTO.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/FileOperateDTO.java @@ -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; /* diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/PageWidgetAddDTO.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/PageWidgetAddDTO.java index 73245088..e87d30ec 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/PageWidgetAddDTO.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/PageWidgetAddDTO.java @@ -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 templates; + /** * 静态化目录 */ @@ -72,4 +84,11 @@ public class PageWidgetAddDTO { * 备注 */ private String remark; + + public Map getPublishPipeTemplateMap() { + if (StringUtils.isEmpty(this.templates)) { + return Map.of(); + } + return this.templates.stream().collect(Collectors.toMap(PublishPipeTemplate::code, PublishPipeTemplate::template)); + } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/PageWidgetEditDTO.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/PageWidgetEditDTO.java index f028fd9b..cfb96976 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/PageWidgetEditDTO.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/PageWidgetEditDTO.java @@ -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 templates; /** * 静态化目录 @@ -70,4 +81,11 @@ public class PageWidgetEditDTO { * 备注 */ private String remark; + + public Map getPublishPipeTemplateMap() { + if (StringUtils.isEmpty(this.templates)) { + return Map.of(); + } + return this.templates.stream().collect(Collectors.toMap(PublishPipeTemplate::code, PublishPipeTemplate::template)); + } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/SiteDTO.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/SiteDTO.java index 41133329..cda0724d 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/SiteDTO.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/SiteDTO.java @@ -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 configProps; - private List publishPipeDatas; + private List publishPipeDatas; private Map params; diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/SiteDefaultTemplateDTO.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/SiteDefaultTemplateDTO.java index daa63973..6ad1a891 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/SiteDefaultTemplateDTO.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/SiteDefaultTemplateDTO.java @@ -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 publishPipeProps; + public List publishPipeProps; } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/PublishPipeProp.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/pojo/PublishPipeProps.java similarity index 59% rename from chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/PublishPipeProp.java rename to chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/pojo/PublishPipeProps.java index 7fab9c54..9e9ecd42 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/dto/PublishPipeProp.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/pojo/PublishPipeProps.java @@ -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 props; - public static PublishPipeProp newInstance(String pipeCode, String pipeName, Map props) { - PublishPipeProp ppp = new PublishPipeProp(); + public static PublishPipeProps newInstance(String pipeCode, String pipeName, Map 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 getProps() { - return props; - } - - public void setProps(Map prop) { - this.props = prop; - } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/pojo/PublishPipeTemplate.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/pojo/PublishPipeTemplate.java new file mode 100644 index 00000000..d48bf525 --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/pojo/PublishPipeTemplate.java @@ -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) { +} diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/CatalogVO.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/CatalogVO.java index 492f04a4..6650fa90 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/CatalogVO.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/CatalogVO.java @@ -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 publishPipeDatas; + private List publishPipeDatas; public static CatalogVO newInstance(CmsCatalog catalog) { CatalogVO dto = new CatalogVO(); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/ContentVO.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/ContentVO.java index 7875cc32..e36d354a 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/ContentVO.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/ContentVO.java @@ -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 publishPipeProps; + private List publishPipeProps; /** * SEO标题 diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/PageWidgetVO.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/PageWidgetVO.java index 14fc6699..9435a7b8 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/PageWidgetVO.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/PageWidgetVO.java @@ -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 templates; + /* * 静态化目录 */ diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/SiteDashboardVO.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/SiteDashboardVO.java new file mode 100644 index 00000000..635ebeaf --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/SiteDashboardVO.java @@ -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 urls; + + public static SiteDashboardVO create(CmsSite site, List publishPipes) { + SiteDashboardVO vo = new SiteDashboardVO(); + vo.setSiteId(site.getSiteId()); + vo.setName(site.getName()); + List urls = publishPipes.stream().map(pp -> PublishPipeProps.newInstance( + pp.getCode(), pp.getName(), Map.of("url", site.getUrl(pp.getCode())) + )).toList(); + vo.setUrls(urls); + return vo; + } +} diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/job/SitePublishJobHandler.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/job/SitePublishJobHandler.java index 927ce40e..402d06c5 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/job/SitePublishJobHandler.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/job/SitePublishJobHandler.java @@ -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; /** * 定时发布任务
@@ -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 sites = this.siteService.list(); + Set catalogIds = new HashSet<>(); for (CmsSite site : sites) { List catalogList = catalogService .list(new LambdaQueryWrapper().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 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; + } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/listener/ContentCoreListener.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/listener/ContentCoreListener.java index d7c401f1..0d462f76 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/listener/ContentCoreListener.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/listener/ContentCoreListener.java @@ -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); } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/FileStorageArgsProperty.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/FileStorageArgsProperty.java index 9aa8bc29..b44ce35c 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/FileStorageArgsProperty.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/FileStorageArgsProperty.java @@ -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; diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/ThumbnailHeightProperty.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/ThumbnailHeightProperty.java index beb85546..b35a0abb 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/ThumbnailHeightProperty.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/ThumbnailHeightProperty.java @@ -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 diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/ThumbnailWidthProperty.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/ThumbnailWidthProperty.java index 6f2c2ea6..76b3d9dc 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/ThumbnailWidthProperty.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/ThumbnailWidthProperty.java @@ -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 diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/SiteStaticizeType.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/SiteStaticizeType.java index ef3dbff3..0bbdc602 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/SiteStaticizeType.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/SiteStaticizeType.java @@ -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}", diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/ICatalogService.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/ICatalogService.java index 7a4de07a..8ea86550 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/ICatalogService.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/ICatalogService.java @@ -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 { * @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); /** * 获取栏目链接 diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/IPublishPipeService.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/IPublishPipeService.java index cb359407..d6e8ed07 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/IPublishPipeService.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/IPublishPipeService.java @@ -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 { * @param props 数据集合 * @return 结果列表 */ - List getPublishPipeProps(Long siteId, PublishPipePropUseType useType, - Map> props); + List getPublishPipeProps(Long siteId, PublishPipePropUseType useType, + Map> props); /** * 获取发布通道属性值 diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/IPublishService.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/IPublishService.java index faa3b743..a991461e 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/IPublishService.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/IPublishService.java @@ -32,14 +32,12 @@ import java.util.List; public interface IPublishService { /** - * 发布站点首页
- *

+ * 发布站点首页
* 此方法供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 { *

* 发布站点下所有栏目及指定状态内容 * - * @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 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); } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/IResourceService.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/IResourceService.java index fc832a78..25254454 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/IResourceService.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/IResourceService.java @@ -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 { - /** - * 获取存储方式 - */ - IFileStorageType getFileStorageType(String type); - /** * 上传资源 */ @@ -102,4 +98,31 @@ public interface IResourceService extends IService { * @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 images, Consumer> thumbnailsConsumer); + + /** + * 处理内容引导图缩略图 + * + * @param site 站点 + * @param imageSrc 源图列表 + * @param thumbnailConsumer 缩略图消费者 + */ + void dealDefaultThumbnail(CmsSite site, String imageSrc, Consumer thumbnailConsumer); } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/CatalogServiceImpl.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/CatalogServiceImpl.java index b984d457..9a0ec4b3 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/CatalogServiceImpl.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/CatalogServiceImpl.java @@ -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> 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 toCatalogs = this.list(q); if (!toCatalogs.isEmpty()) { for (CmsCatalog toCatalog : toCatalogs) { - List publishPipeProps = dto.getPublishPipeProps(); - for (PublishPipeProp publishPipeProp : publishPipeProps) { + List publishPipeProps = dto.getPublishPipeProps(); + for (PublishPipeProps publishPipeProp : publishPipeProps) { Map sitePublishPipeProp = site.getPublishPipeProps() .get(publishPipeProp.getPipeCode()); Map catalogPublishPipeProp = toCatalog @@ -604,6 +603,9 @@ public class CatalogServiceImpl extends ServiceImpl icontent = contentType.loadContent(content); icontent.setOperator(operator); + icontent.getParams().put(IContent.PARAM_IS_DELETE_BY_CATALOG, true); icontent.delete(); }); } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/FileServiceImpl.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/FileServiceImpl.java index 24fa6902..a2fbd7b5 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/FileServiceImpl.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/FileServiceImpl.java @@ -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(); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/PublishPipeServiceImpl.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/PublishPipeServiceImpl.java index ca5b05c8..665a0307 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/PublishPipeServiceImpl.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/PublishPipeServiceImpl.java @@ -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 publishPipeProps; @Override - public List getPublishPipeProps(Long siteId, PublishPipePropUseType useType, - Map> props) { + public List getPublishPipeProps(Long siteId, PublishPipePropUseType useType, + Map> props) { List 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())) { diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/PublishServiceImpl.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/PublishServiceImpl.java index 5f8f83cf..7b5198bb 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/PublishServiceImpl.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/PublishServiceImpl.java @@ -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 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); } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/ResourceServiceImpl.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/ResourceServiceImpl.java index 7e899562..09d79c28 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/ResourceServiceImpl.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/ResourceServiceImpl.java @@ -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 implements IResourceService { - private final Map fileStorageTypes; + private final FileStorageService fileStorageService; private final List resourceStats; @@ -144,23 +147,42 @@ public class ResourceServiceImpl extends ServiceImpl 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 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 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 StorageErrorCode.UNSUPPORTED_STORAGE_TYPE.exception(type)); - return fileStorageType; - } - /** * 解析html中的图片标签,如果是远程地址图片则下载到资源库中并将图片标签的src替换为资源内部链接 * @@ -359,13 +371,83 @@ public class ResourceServiceImpl extends ServiceImpl internalUrls, Consumer> consumer) { + if (internalUrls.isEmpty()) { + return; + } + int w = ThumbnailWidthProperty.getValue(site.getConfigProps()); + int h = ThumbnailHeightProperty.getValue(site.getConfigProps()); + if (w > 0 && h > 0) { + List thumbnails = dealThumbnails(internalUrls, w, h); + consumer.accept(thumbnails); + } + } + + @Override + public void dealDefaultThumbnail(CmsSite site, String internalUrl, Consumer consumer) { + if (StringUtils.isEmpty(internalUrl)) { + return; + } + dealDefaultThumbnail(site, List.of(internalUrl), thumbnails -> consumer.accept(thumbnails.get(0))); + } + + private List dealThumbnails(List 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 quotedResources = new HashMap<>(); for (IResourceStat resourceStat : this.resourceStats) { - resourceStat.statQuotedResource(siteId, quotedResources); + resourceStat.statQuotedResource(siteId, quotedResources); } } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/SiteServiceImpl.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/SiteServiceImpl.java index f17fa9c4..769a9875 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/SiteServiceImpl.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/SiteServiceImpl.java @@ -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 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 impleme prop.getProps().entrySet().removeIf(e -> !publishPipeProps.containsKey(IPublishPipeProp.BEAN_PREFIX + e.getKey())); }); Map> 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 impleme @Override public void saveSiteDefaultTemplate(SiteDefaultTemplateDTO dto) { CmsSite site = this.getSite(dto.getSiteId()); - List publishPipeProps = dto.getPublishPipeProps(); - for (PublishPipeProp ppp : publishPipeProps) { + List publishPipeProps = dto.getPublishPipeProps(); + for (PublishPipeProps ppp : publishPipeProps) { Map sitePublishPipeProps = site.getPublishPipeProps(ppp.getPipeCode()); sitePublishPipeProps.putAll(ppp.getProps()); } @@ -245,6 +253,13 @@ public class SiteServiceImpl extends ServiceImpl 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 diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/SiteThemeService.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/SiteThemeService.java index fa454b4b..5aae2024 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/SiteThemeService.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/SiteThemeService.java @@ -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); + } } } }; diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/func/FileExtractorFunction.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/func/FileExtractorFunction.java index c5035a92..6bced778 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/func/FileExtractorFunction.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/func/FileExtractorFunction.java @@ -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 suffixArray = List.of(); if (args.length == 2 && Objects.nonNull(args[1])) { diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/func/ImageSizeFunction.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/func/ImageSizeFunction.java index 60520617..23bd8793 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/func/ImageSizeFunction.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/func/ImageSizeFunction.java @@ -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) ); } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/tag/CmsPageWidgetTag.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/tag/CmsPageWidgetTag.java index 2823cb97..891231f4 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/tag/CmsPageWidgetTag.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/tag/CmsPageWidgetTag.java @@ -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) { // 读取页面部件静态化内容 diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/ConfigPropertyUtils.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/ConfigPropertyUtils.java index 9de78af2..5bd35dfc 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/ConfigPropertyUtils.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/ConfigPropertyUtils.java @@ -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 firstProps, + Map 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); + } } } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/FileStorageHelper.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/FileStorageHelper.java index c5a00641..43baa303 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/FileStorageHelper.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/FileStorageHelper.java @@ -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 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); + } + } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/InternalUrlUtils.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/InternalUrlUtils.java index 3a0d4572..dd64ed32 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/InternalUrlUtils.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/InternalUrlUtils.java @@ -94,6 +94,10 @@ public class InternalUrlUtils { return getActualUrl(iurl, null, true); } + public static String getActualPreviewUrl(InternalURL internalURL) { + return getActualUrl(internalURL, null, true); + } + /** * 判断是否是内部资源URL */ diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/TemplateUtils.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/TemplateUtils.java index 7e56e730..433a56d6 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/TemplateUtils.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/TemplateUtils.java @@ -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); + } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages.properties b/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages.properties index 832655e8..9a6e30bb 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages.properties +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages.properties @@ -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=回收站过期内容删除任务 diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_en.properties b/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_en.properties index ae0fbd7d..6de492c8 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_en.properties +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_en.properties @@ -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 diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_zh_TW.properties b/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_zh_TW.properties index f2b0b0c8..ecb8c5ae 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_zh_TW.properties +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_zh_TW.properties @@ -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=資源回收筒過期內容刪除任務 diff --git a/chestnut-cms/chestnut-cms-customform/pom.xml b/chestnut-cms/chestnut-cms-customform/pom.xml index e8a59746..7c954da9 100644 --- a/chestnut-cms/chestnut-cms-customform/pom.xml +++ b/chestnut-cms/chestnut-cms-customform/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-customform diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/cache/CustomFormCaptchaMonitoredCache.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/cache/CustomFormCaptchaMonitoredCache.java deleted file mode 100644 index 4ff8d8ca..00000000 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/cache/CustomFormCaptchaMonitoredCache.java +++ /dev/null @@ -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 { - - 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); - } -} diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/CustomFormController.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/CustomFormController.java index 96ddc68b..00d83ebb 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/CustomFormController.java +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/CustomFormController.java @@ -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; /** *

@@ -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> 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 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); } diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/front/CustomFormApiController.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/front/CustomFormApiController.java index a290f6d4..1840e60f 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/front/CustomFormApiController.java +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/front/CustomFormApiController.java @@ -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; /** *

@@ -73,14 +61,12 @@ public class CustomFormApiController extends BaseRestController { private final ICustomFormApiService customFormApiService; - private final CustomFormCaptchaMonitoredCache captchaCache; - - private final Map 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(); + } } } diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/domain/dto/CustomFormEditDTO.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/domain/dto/CustomFormEditDTO.java index 7197f69f..5d7c556e 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/domain/dto/CustomFormEditDTO.java +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/domain/dto/CustomFormEditDTO.java @@ -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> templates; + private List templates; /** * 备注 diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/domain/vo/CustomFormVO.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/domain/vo/CustomFormVO.java index 1f54f53b..077c4561 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/domain/vo/CustomFormVO.java +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/domain/vo/CustomFormVO.java @@ -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> templates; + private List templates; public static CustomFormVO from(CmsCustomForm form) { CustomFormVO vo = new CustomFormVO(); diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/impl/CustomFormServiceImpl.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/impl/CustomFormServiceImpl.java index 652394b1..2ee8f443 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/impl/CustomFormServiceImpl.java +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/impl/CustomFormServiceImpl.java @@ -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 form.getTemplates().put(item.get("code"), item.get("template"))); + form.setTemplates(dto.getTemplates().stream().collect(Collectors.toMap(PublishPipeTemplate::code, PublishPipeTemplate::template))); this.updateById(form); } diff --git a/chestnut-cms/chestnut-cms-dynamic/pom.xml b/chestnut-cms/chestnut-cms-dynamic/pom.xml index 3b657b17..1ff8c34b 100644 --- a/chestnut-cms/chestnut-cms-dynamic/pom.xml +++ b/chestnut-cms/chestnut-cms-dynamic/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-dynamic diff --git a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/DynamicCoreHandler.java b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/DynamicCoreHandler.java index a576d93e..1f84c637 100644 --- a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/DynamicCoreHandler.java +++ b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/DynamicCoreHandler.java @@ -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); diff --git a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/IDynamicPageInitData.java b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/IDynamicPageInitData.java index 2fe80428..01b7ffe2 100644 --- a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/IDynamicPageInitData.java +++ b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/IDynamicPageInitData.java @@ -29,7 +29,7 @@ public interface IDynamicPageInitData { String BEAN_PREFIX = "DynamicPageInitData."; - void initTemplateData(TemplateContext context, Map parameters); + void initTemplateData(TemplateContext context, String path, Map parameters); String getType(); diff --git a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/impl/MemberDynamicPageInitData.java b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/impl/MemberDynamicPageInitData.java index 3a8916c5..11c8c5bb 100644 --- a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/impl/MemberDynamicPageInitData.java +++ b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/impl/MemberDynamicPageInitData.java @@ -47,7 +47,7 @@ public class MemberDynamicPageInitData implements IDynamicPageInitData { } @Override - public void initTemplateData(TemplateContext context, Map parameters) { + public void initTemplateData(TemplateContext context, String path, Map parameters) { if (StpMemberUtil.isLogin()) { LoginUser loginUser = StpMemberUtil.getLoginUser(); context.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER, loginUser.getUser()); diff --git a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/impl/PaginationDynamicPageInitData.java b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/impl/PaginationDynamicPageInitData.java index 6347c3a5..b27727cf 100644 --- a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/impl/PaginationDynamicPageInitData.java +++ b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/impl/PaginationDynamicPageInitData.java @@ -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 parameters) { - int pageIndex = MapUtils.getIntValue(parameters, "pi", 1); + public void initTemplateData(TemplateContext context, String path, Map 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)); } } diff --git a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/service/IDynamicPageService.java b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/service/IDynamicPageService.java index 0137ef4f..cc33fe86 100644 --- a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/service/IDynamicPageService.java +++ b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/service/IDynamicPageService.java @@ -31,5 +31,11 @@ public interface IDynamicPageService extends IService { void deleteDynamicPage(List dynamicPageIds); - void generateDynamicPage(String uri, Long siteId, String publishPipeCode, Boolean preview, Map 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 parameters, HttpServletResponse response) throws IOException; } diff --git a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/service/impl/DynamicPageServiceImpl.java b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/service/impl/DynamicPageServiceImpl.java index b0edc3a5..f365f642 100644 --- a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/service/impl/DynamicPageServiceImpl.java +++ b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/service/impl/DynamicPageServiceImpl.java @@ -79,7 +79,7 @@ public class DynamicPageServiceImpl extends ServiceImpl 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 { - this.unregisterDynamicPageMapping(dynamicPage); dynamicPageHelper.clearCache(dynamicPage); + this.unregisterDynamicPageMapping(dynamicPage.getCode(), dynamicPage.getPath()); }); } @@ -118,25 +123,34 @@ public class DynamicPageServiceImpl extends ServiceImpl 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 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 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 { dynamicPageHelper.updateCache(dynamicPage); - this.registerDynamicPageMapping(dynamicPage); + this.registerDynamicPageMapping(dynamicPage.getCode(), dynamicPage.getPath()); }); } @@ -206,7 +221,7 @@ public class DynamicPageServiceImpl extends ServiceImpl com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-exmodel diff --git a/chestnut-cms/chestnut-cms-exmodel/src/main/java/com/chestnut/exmodel/controller/EXModelController.java b/chestnut-cms/chestnut-cms-exmodel/src/main/java/com/chestnut/exmodel/controller/EXModelController.java index 03d2843a..d9d14090 100644 --- a/chestnut-cms/chestnut-cms-exmodel/src/main/java/com/chestnut/exmodel/controller/EXModelController.java +++ b/chestnut-cms/chestnut-cms-exmodel/src/main/java/com/chestnut/exmodel/controller/EXModelController.java @@ -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 q = new LambdaQueryWrapper() .eq(XModel::getOwnerType, CmsExtendMetaModelType.TYPE) .eq(XModel::getOwnerId, site.getSiteId()); diff --git a/chestnut-cms/chestnut-cms-image/pom.xml b/chestnut-cms/chestnut-cms-image/pom.xml index bd187024..45ca8b2f 100644 --- a/chestnut-cms/chestnut-cms-image/pom.xml +++ b/chestnut-cms/chestnut-cms-image/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-image diff --git a/chestnut-cms/chestnut-cms-image/src/main/java/com/chestnut/cms/image/ImageContentType.java b/chestnut-cms/chestnut-cms-image/src/main/java/com/chestnut/cms/image/ImageContentType.java index b704c443..9d188ac0 100644 --- a/chestnut-cms/chestnut-cms-image/src/main/java/com/chestnut/cms/image/ImageContentType.java +++ b/chestnut-cms/chestnut-cms-image/src/main/java/com/chestnut/cms/image/ImageContentType.java @@ -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 publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(), + List 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 publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(), + List 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; } diff --git a/chestnut-cms/chestnut-cms-link/pom.xml b/chestnut-cms/chestnut-cms-link/pom.xml index 0bf3e158..6c2845ab 100644 --- a/chestnut-cms/chestnut-cms-link/pom.xml +++ b/chestnut-cms/chestnut-cms-link/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-link diff --git a/chestnut-cms/chestnut-cms-media/pom.xml b/chestnut-cms/chestnut-cms-media/pom.xml index 166ebc7d..2ce95b6c 100644 --- a/chestnut-cms/chestnut-cms-media/pom.xml +++ b/chestnut-cms/chestnut-cms-media/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-media diff --git a/chestnut-cms/chestnut-cms-media/src/main/java/com/chestnut/media/AudioContentType.java b/chestnut-cms/chestnut-cms-media/src/main/java/com/chestnut/media/AudioContentType.java index efcfba7c..cfacfb4d 100644 --- a/chestnut-cms/chestnut-cms-media/src/main/java/com/chestnut/media/AudioContentType.java +++ b/chestnut-cms/chestnut-cms-media/src/main/java/com/chestnut/media/AudioContentType.java @@ -24,16 +24,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.media.domain.BCmsAudio; import com.chestnut.media.domain.CmsAudio; @@ -57,6 +52,8 @@ public class AudioContentType implements IContentType { private final static String NAME = "{CMS.CONTENTCORE.CONTENT_TYPE." + ID + "}"; + private final ISiteService siteService; + private final IContentService contentService; private final IAudioService audioService; @@ -65,6 +62,8 @@ public class AudioContentType implements IContentType { private final IPublishPipeService publishPipeService; + private final IResourceService resourceService; + @Override public String getId() { return ID; @@ -134,7 +133,7 @@ public class AudioContentType implements IContentType { }); vo = AudioAlbumVO.newInstance(contentEntity, list); // 发布通道数据 - List publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(), + List publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(), PublishPipePropUseType.Content, contentEntity.getPublishPipeProps()); vo.setPublishPipeProps(publishPipeProps); } else { @@ -145,11 +144,17 @@ public class AudioContentType implements IContentType { // 发布通道初始数据 vo.setPublishPipe(publishPipes.stream().map(CmsPublishPipe::getCode).toArray(String[]::new)); // 发布通道数据 - List publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(), + List 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; } diff --git a/chestnut-cms/chestnut-cms-media/src/main/java/com/chestnut/media/VideoContentType.java b/chestnut-cms/chestnut-cms-media/src/main/java/com/chestnut/media/VideoContentType.java index 3453dd47..819b5b1b 100644 --- a/chestnut-cms/chestnut-cms-media/src/main/java/com/chestnut/media/VideoContentType.java +++ b/chestnut-cms/chestnut-cms-media/src/main/java/com/chestnut/media/VideoContentType.java @@ -25,16 +25,11 @@ import com.chestnut.common.utils.StringUtils; 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.media.domain.BCmsVideo; import com.chestnut.media.domain.CmsVideo; @@ -58,6 +53,8 @@ public class VideoContentType implements IContentType { private final static String NAME = "{CMS.CONTENTCORE.CONTENT_TYPE." + ID + "}"; + private final ISiteService siteService; + private final IContentService contentService; private final IVideoService videoService; @@ -66,6 +63,8 @@ public class VideoContentType implements IContentType { private final IPublishPipeService publishPipeService; + private final IResourceService resourceService; + @Override public String getId() { return ID; @@ -138,7 +137,7 @@ public class VideoContentType implements IContentType { }); vo = VideoAlbumVO.newInstance(contentEntity, list); // 发布通道模板数据 - List publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(), + List publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(), PublishPipePropUseType.Content, contentEntity.getPublishPipeProps()); vo.setPublishPipeProps(publishPipeProps); } else { @@ -149,11 +148,17 @@ public class VideoContentType implements IContentType { // 发布通道初始数据 vo.setPublishPipe(publishPipes.stream().map(CmsPublishPipe::getCode).toArray(String[]::new)); // 发布通道模板数据 - List publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(), + List 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; } diff --git a/chestnut-cms/chestnut-cms-member/pom.xml b/chestnut-cms/chestnut-cms-member/pom.xml index d15ad194..7e270f4d 100644 --- a/chestnut-cms/chestnut-cms-member/pom.xml +++ b/chestnut-cms/chestnut-cms-member/pom.xml @@ -6,7 +6,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-member diff --git a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/domain/vo/MemberContentVO.java b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/domain/vo/MemberContentVO.java index 9030ee9a..4283da75 100644 --- a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/domain/vo/MemberContentVO.java +++ b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/domain/vo/MemberContentVO.java @@ -17,7 +17,7 @@ package com.chestnut.cms.member.domain.vo; import com.chestnut.contentcore.domain.CmsContent; 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; @@ -214,7 +214,7 @@ public class MemberContentVO implements InitByContent { /** * 发布通道配置 */ - private List publishPipeProps; + private List publishPipeProps; /** * SEO标题 diff --git a/chestnut-cms/chestnut-cms-search/pom.xml b/chestnut-cms/chestnut-cms-search/pom.xml index f531692c..ac9d6479 100644 --- a/chestnut-cms/chestnut-cms-search/pom.xml +++ b/chestnut-cms/chestnut-cms-search/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-search diff --git a/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/controller/front/SearchApiController.java b/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/controller/front/SearchApiController.java index e83d0c05..82594b5b 100644 --- a/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/controller/front/SearchApiController.java +++ b/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/controller/front/SearchApiController.java @@ -23,6 +23,7 @@ import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption; import co.elastic.clients.elasticsearch.core.search.Suggestion; import com.chestnut.cms.search.CmsSearchConstants; +import com.chestnut.cms.search.template.tag.CmsSearchContentTag; import com.chestnut.cms.search.vo.ESContentVO; import com.chestnut.common.domain.R; import com.chestnut.common.security.web.BaseRestController; @@ -279,8 +280,11 @@ public class SearchApiController extends BaseRestController { @GetMapping("/group/catalog") public R groupBy(@RequestParam("sid") @LongId Long siteId, @RequestParam(value = "q") @Length(max = 50) String query, + @RequestParam(value = "cid", defaultValue = "0") Long catalogId, + @RequestParam(value = "level", defaultValue = "0") Integer level, @RequestParam(value = "ot", required = false ,defaultValue = "false") Boolean onlyTitle, @RequestParam(value = "ct", required = false) String contentType) throws ElasticsearchException, IOException { + CmsCatalog catalog = IdUtils.validate(catalogId) ? catalogService.getCatalog(catalogId) : null; String indexName = CmsSearchConstants.indexName(siteId.toString()); SearchResponse sr = esClient.search(s -> s.index(indexName) @@ -308,6 +312,13 @@ public class SearchApiController extends BaseRestController { ); } } + if (Objects.nonNull(catalog)) { + if (CmsSearchContentTag.SearchLevel.CurrentAndChild.ordinal() == level) { + b.must(must -> must.prefix(prefixFn -> prefixFn.field("catalogAncestors").value(catalog.getAncestors()))); + } else { + b.must(must -> must.term(tq -> tq.field("catalogId").value(catalog.getCatalogId()))); + } + } return b; }) ) @@ -318,14 +329,14 @@ public class SearchApiController extends BaseRestController { .size(0), ObjectNode.class); Aggregate aggregate = sr.aggregations().get("groupBy"); List list = aggregate.lterms().buckets().array().stream().map(b -> { - Long catalogId = b.key(); - CmsCatalog catalog = catalogService.getCatalog(b.key()); - if (Objects.isNull(catalog)) { + CmsCatalog _catalog = catalogService.getCatalog(b.key()); + if (Objects.isNull(_catalog)) { return null; } return JacksonUtils.objectNode() - .put("id", catalogId) - .put("name", catalog.getName()) + .put("id", _catalog.getCatalogId()) + .put("ancestors", _catalog.getAncestors()) + .put("name", _catalog.getName()) .put("total", b.docCount()); }).filter(Objects::nonNull).toList(); return R.ok(list); diff --git a/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/service/ContentIndexService.java b/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/service/ContentIndexService.java index c634e8ac..7d200b85 100644 --- a/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/service/ContentIndexService.java +++ b/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/service/ContentIndexService.java @@ -30,6 +30,7 @@ import com.chestnut.cms.search.fixed.config.SearchAnalyzeType; import com.chestnut.cms.search.properties.EnableIndexProperty; import com.chestnut.common.async.AsyncTask; import com.chestnut.common.async.AsyncTaskManager; +import com.chestnut.common.i18n.I18nUtils; import com.chestnut.common.utils.ArrayUtils; import com.chestnut.common.utils.Assert; import com.chestnut.common.utils.StringUtils; @@ -187,7 +188,7 @@ public class ContentIndexService implements CommandLineRunner { .doc(newESContentDoc(content)) .docAsUpsert(true), ESContent.class); } catch (ElasticsearchException | IOException e) { - AsyncTaskManager.addErrMessage(e.getMessage()); + AsyncTaskManager.addErrMessage(e.getMessage() + ", contentId=" + content.getContentEntity().getContentId()); log.error("Update es index document failed", e); } } @@ -251,7 +252,7 @@ public class ContentIndexService implements CommandLineRunner { for (int i = 0; i * pageSize < total; i++) { try { AsyncTaskManager.setTaskProgressInfo((int) (count++ * 100 / total), - "正在重建栏目【" + catalog.getName() + "】内容索引"); + I18nUtils.parse("PROGRESS.INFO.BUILDING_INDEX", catalog.getName())); AsyncTaskManager.checkInterrupt(); // 允许中断 Page page = contentService.dao().page(new Page<>(i, pageSize, false), q); batchContentDoc(site, catalog, page.getRecords()); @@ -286,7 +287,7 @@ public class ContentIndexService implements CommandLineRunner { for (CmsCatalog catalog : catalogs) { rebuildCatalog(catalog, false); } - this.setProgressInfo(100, "重建全站索引完成"); + this.setProgressInfo(100, I18nUtils.parse("PROGRESS.INFO.BUILD_SUCCEED")); } catch (Exception e) { log.error("RebuildAllContentIndex failed.", e); addErrorMessage(e.getMessage()); diff --git a/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/template/tag/CmsSearchContentTag.java b/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/template/tag/CmsSearchContentTag.java index a2746726..d009f756 100644 --- a/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/template/tag/CmsSearchContentTag.java +++ b/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/template/tag/CmsSearchContentTag.java @@ -27,6 +27,8 @@ import com.chestnut.common.staticize.tag.TagAttrOption; import com.chestnut.common.utils.IdUtils; import com.chestnut.common.utils.JacksonUtils; import com.chestnut.common.utils.StringUtils; +import com.chestnut.contentcore.domain.CmsCatalog; +import com.chestnut.contentcore.service.ICatalogService; import com.chestnut.contentcore.util.TemplateUtils; import com.chestnut.exmodel.CmsExtendMetaModelType; import com.chestnut.search.SearchConsts; @@ -56,6 +58,9 @@ public class CmsSearchContentTag extends AbstractListTag { public final static String DESC = "{FREEMARKER.TAG." + TAG_NAME + ".DESC}"; public final static String ATTR_USAGE_QUERY = "{FREEMARKER.TAG." + TAG_NAME + ".query}"; public final static String ATTR_USAGE_CATALOG_ID = "{FREEMARKER.TAG." + TAG_NAME + ".catalogId}"; + public final static String ATTR_USAGE_LEVEL = "{FREEMARKER.TAG." + TAG_NAME + ".level}"; + public final static String ATTR_OPTION_LEVEL_CURRENT = "{FREEMARKER.TAG." + TAG_NAME + ".level.Current}"; + public final static String ATTR_OPTION_LEVEL_CURRENT_AND_CHILD = "{FREEMARKER.TAG." + TAG_NAME + ".level.CurrentAndChild}"; public final static String ATTR_USAGE_CONTENT_TYPE = "{FREEMARKER.TAG." + TAG_NAME + ".contentType}"; public final static String ATTR_USAGE_MODE = "{FREEMARKER.TAG." + TAG_NAME + ".mode}"; public final static String ATTR_OPTION_MODE_FULL_TEXT = "{FREEMARKER.TAG." + TAG_NAME + ".mode.FullText}"; @@ -65,16 +70,21 @@ public class CmsSearchContentTag extends AbstractListTag { private final static String ATTR_QUERY = "query"; private final static String ATTR_CATALOG_ID = "catalogid"; + private final static String ATTR_LEVEL = "level"; private final static String ATTR_CONTENT_TYPE = "contenttype"; private final static String ATTR_MODE = "mode"; private final ElasticsearchClient esClient; + private final ICatalogService catalogService; + @Override public List getTagAttrs() { List tagAttrs = super.getTagAttrs(); tagAttrs.add(new TagAttr(ATTR_QUERY, false, TagAttrDataType.STRING, ATTR_USAGE_QUERY)); tagAttrs.add(new TagAttr(ATTR_CATALOG_ID, false, TagAttrDataType.STRING, ATTR_USAGE_CATALOG_ID)); + tagAttrs.add(new TagAttr(ATTR_LEVEL, false, TagAttrDataType.STRING, ATTR_USAGE_LEVEL, + SearchLevel.toTagAttrOptions(), SearchLevel.Current.name())); tagAttrs.add(new TagAttr(ATTR_CONTENT_TYPE, false, TagAttrDataType.STRING, ATTR_USAGE_CONTENT_TYPE)); tagAttrs.add(new TagAttr(ATTR_MODE, false, TagAttrDataType.STRING, ATTR_USAGE_MODE, SearchMode.toTagAttrOptions(), SearchMode.FullText.name())); @@ -86,11 +96,14 @@ public class CmsSearchContentTag extends AbstractListTag { Long siteId = TemplateUtils.evalSiteId(env); String mode = MapUtils.getString(attrs, ATTR_MODE, SearchMode.FullText.name()); String query = MapUtils.getString(attrs, ATTR_QUERY); -// if (StringUtils.isEmpty(query)) { + if (StringUtils.isEmpty(query)) { + return TagPageData.of(List.of(), 0); // throw new TemplateException("Tag attr `query` cannot be empty.", env); -// } + } String contentType = MapUtils.getString(attrs, ATTR_CONTENT_TYPE); - Long catalogId = MapUtils.getLong(attrs, ATTR_CATALOG_ID); + Long catalogId = MapUtils.getLong(attrs, ATTR_CATALOG_ID, 0L); + CmsCatalog catalog = IdUtils.validate(catalogId) ? catalogService.getCatalog(catalogId) : null; + int level = MapUtils.getIntValue(attrs, ATTR_LEVEL, SearchLevel.Current.ordinal()); try { SearchResponse sr = esClient.search(s -> { s.index(CmsSearchConstants.indexName(siteId.toString())) // 索引 @@ -100,8 +113,12 @@ public class CmsSearchContentTag extends AbstractListTag { if (StringUtils.isNotEmpty(contentType)) { b.must(must -> must.term(tq -> tq.field("contentType").value(contentType))); } - if (IdUtils.validate(catalogId)) { - b.must(must -> must.term(tq -> tq.field("catalogId").value(catalogId))); + if (Objects.nonNull(catalog)) { + if (SearchLevel.CurrentAndChild.ordinal() == level) { + b.must(must -> must.prefix(prefixFn -> prefixFn.field("catalogAncestors").value(catalog.getAncestors()))); + } else { + b.must(must -> must.term(tq -> tq.field("catalogId").value(catalog.getCatalogId()))); + } } if (StringUtils.isNotEmpty(query)) { if (SearchMode.isFullText(mode)) { @@ -208,11 +225,11 @@ public class CmsSearchContentTag extends AbstractListTag { } private enum SearchMode { - // 所有站点 + // 全文检索 FullText(ATTR_OPTION_MODE_FULL_TEXT), - // 当前站点 + // 标签检索或 Tag(ATTR_OPTION_MODE_TAG), - // 当前站点 + // 标签检索且 TagAnd(ATTR_OPTION_MODE_TAG_AND); private final String desc; @@ -241,4 +258,26 @@ public class CmsSearchContentTag extends AbstractListTag { ); } } + + public enum SearchLevel { + Current(ATTR_OPTION_LEVEL_CURRENT), + CurrentAndChild(ATTR_OPTION_LEVEL_CURRENT_AND_CHILD); + + private final String desc; + + SearchLevel(String desc) { + this.desc = desc; + } + + static boolean isCurrent(String mode) { + return Current.name().equalsIgnoreCase(mode); + } + + static List toTagAttrOptions() { + return List.of( + new TagAttrOption(Current.name(), Current.desc), + new TagAttrOption(CurrentAndChild.name(), CurrentAndChild.desc) + ); + } + } } \ No newline at end of file diff --git a/chestnut-cms/chestnut-cms-search/src/main/resources/i18n/messages.properties b/chestnut-cms/chestnut-cms-search/src/main/resources/i18n/messages.properties index 579f933f..99203239 100644 --- a/chestnut-cms/chestnut-cms-search/src/main/resources/i18n/messages.properties +++ b/chestnut-cms/chestnut-cms-search/src/main/resources/i18n/messages.properties @@ -9,6 +9,9 @@ FREEMARKER.TAG.cms_search_content.mode=检索方式 FREEMARKER.TAG.cms_search_content.mode.FullText=全文检索 FREEMARKER.TAG.cms_search_content.mode.Tag=标签或检索,多个标签英文逗号隔开 FREEMARKER.TAG.cms_search_content.mode.TagAnd=标签与检索,多个标签英文逗号隔开 +FREEMARKER.TAG.cms_search_content.level=数据获取范围(V1.5.6+) +FREEMARKER.TAG.cms_search_content.level.Current=当前栏目 +FREEMARKER.TAG.cms_search_content.level.CurrentAndChild=当前栏目和子栏目 FREEMARKER.TAG.cms_search_word.NAME=搜索热词标签 FREEMARKER.TAG.cms_search_word.DESC=搜索热词标签 @@ -19,4 +22,7 @@ DYNAMIC_PAGE_TYPE.Search.ARG.q=搜索词 DYNAMIC_PAGE_TYPE.Search.ARG.cid=栏目ID DYNAMIC_PAGE_TYPE.Search.ARG.ot=是否只搜索标题 DYNAMIC_PAGE_TYPE.Search.ARG.ct=内容类型 -DYNAMIC_PAGE_TYPE.Search.ARG.page=页码 \ No newline at end of file +DYNAMIC_PAGE_TYPE.Search.ARG.page=页码 + +PROGRESS.INFO.BUILDING_INDEX=正在重建栏目索引:{0} +PROGRESS.INFO.BUILD_SUCCEED=重建索引完成 \ No newline at end of file diff --git a/chestnut-cms/chestnut-cms-search/src/main/resources/i18n/messages_en.properties b/chestnut-cms/chestnut-cms-search/src/main/resources/i18n/messages_en.properties index 43536c48..4579acb2 100644 --- a/chestnut-cms/chestnut-cms-search/src/main/resources/i18n/messages_en.properties +++ b/chestnut-cms/chestnut-cms-search/src/main/resources/i18n/messages_en.properties @@ -9,6 +9,9 @@ FREEMARKER.TAG.cms_search_content.mode=Search Mode FREEMARKER.TAG.cms_search_content.mode.FullText=Full text FREEMARKER.TAG.cms_search_content.mode.Tag=Tag Or, multiple tags separated by `,`. FREEMARKER.TAG.cms_search_content.mode.TagAnd=Tag And, multiple tags separated by `,`. +FREEMARKER.TAG.cms_search_content.level=Data Scope (V1.5.6+) +FREEMARKER.TAG.cms_search_content.level.Current=Current catalog. +FREEMARKER.TAG.cms_search_content.level.CurrentAndChild=Current catalog and children. FREEMARKER.TAG.cms_search_word.NAME=Search hot word tag FREEMARKER.TAG.cms_search_word.DESC=Search hot word tag @@ -19,4 +22,6 @@ DYNAMIC_PAGE_TYPE.Search.ARG.q=Search query DYNAMIC_PAGE_TYPE.Search.ARG.cid=Catalog id DYNAMIC_PAGE_TYPE.Search.ARG.ot=Search only the title field DYNAMIC_PAGE_TYPE.Search.ARG.ct=Content type -DYNAMIC_PAGE_TYPE.Search.ARG.page=Page number \ No newline at end of file +DYNAMIC_PAGE_TYPE.Search.ARG.page=Page number + +PROGRESS.INFO.BUILDING_CATALOG_INDEX=Rebuilding index for catalog: {0} \ No newline at end of file diff --git a/chestnut-cms/chestnut-cms-search/src/main/resources/i18n/messages_zh_TW.properties b/chestnut-cms/chestnut-cms-search/src/main/resources/i18n/messages_zh_TW.properties index b9c7e518..4e5c0bcc 100644 --- a/chestnut-cms/chestnut-cms-search/src/main/resources/i18n/messages_zh_TW.properties +++ b/chestnut-cms/chestnut-cms-search/src/main/resources/i18n/messages_zh_TW.properties @@ -9,6 +9,9 @@ FREEMARKER.TAG.cms_search_content.mode=檢索方式 FREEMARKER.TAG.cms_search_content.mode.FullText=全文檢索 FREEMARKER.TAG.cms_search_content.mode.Tag=標籤或檢索,多個標籤英文逗號隔開 FREEMARKER.TAG.cms_search_content.mode.TagAnd=標籤與檢索,多個標籤英文逗號隔開 +FREEMARKER.TAG.cms_search_content.level=數據獲取範圍(V1.5.6+) +FREEMARKER.TAG.cms_search_content.level.Current=當前欄目 +FREEMARKER.TAG.cms_search_content.level.CurrentAndChild=當前欄目和子欄目 FREEMARKER.TAG.cms_search_word.NAME=檢索詞熱詞標籤 FREEMARKER.TAG.cms_search_word.DESC=檢索詞熱詞標籤 @@ -20,3 +23,5 @@ DYNAMIC_PAGE_TYPE.Search.ARG.cid=欄目ID DYNAMIC_PAGE_TYPE.Search.ARG.ot=是否只搜索標題 DYNAMIC_PAGE_TYPE.Search.ARG.ct=內容類型 DYNAMIC_PAGE_TYPE.Search.ARG.page=頁碼 + +PROGRESS.INFO.BUILDING_INDEX=正在重建欄目索引:{0} diff --git a/chestnut-cms/chestnut-cms-seo/pom.xml b/chestnut-cms/chestnut-cms-seo/pom.xml index aa7a6de2..d6615d6f 100644 --- a/chestnut-cms/chestnut-cms-seo/pom.xml +++ b/chestnut-cms/chestnut-cms-seo/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-seo diff --git a/chestnut-cms/chestnut-cms-stat/pom.xml b/chestnut-cms/chestnut-cms-stat/pom.xml index 79c08dcc..3797a180 100644 --- a/chestnut-cms/chestnut-cms-stat/pom.xml +++ b/chestnut-cms/chestnut-cms-stat/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-stat diff --git a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/CatalogContentCountByStatus.java b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/job/CatalogContentCountByStatus.java similarity index 87% rename from chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/CatalogContentCountByStatus.java rename to chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/job/CatalogContentCountByStatus.java index ac704868..9a4147ce 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/CatalogContentCountByStatus.java +++ b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/job/CatalogContentCountByStatus.java @@ -1,120 +1,113 @@ -/* - * 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.cms.stat; - -import com.chestnut.cms.stat.domain.CmsCatalogContentStat; -import com.chestnut.cms.stat.mapper.CmsCatalogContentStatMapper; -import com.chestnut.common.utils.IdUtils; -import com.chestnut.contentcore.domain.CmsCatalog; -import com.chestnut.contentcore.domain.CmsContent; -import com.chestnut.contentcore.fixed.dict.ContentStatus; -import com.chestnut.contentcore.service.ICatalogService; -import com.chestnut.contentcore.service.IContentService; -import jakarta.annotation.PreDestroy; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.CommandLineRunner; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.scheduling.support.PeriodicTrigger; -import org.springframework.stereotype.Component; - -import java.time.Duration; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; - -/** - * 栏目内容数量状态分组统计,5分钟定时更新有变更的栏目数据到数据库 - * - * @author 兮玥 - * @email 190785909@qq.com - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class CatalogContentCountByStatus implements CommandLineRunner { - - private final ICatalogService catalogService; - - private final IContentService contentService; - - private final CmsCatalogContentStatMapper catalogContentStatMapper; - - private final ThreadPoolTaskScheduler threadPoolTaskScheduler; - - private static final Set changeCatalogIds = new HashSet<>(); - - public synchronized void triggerChange(Long catalogId) { - if (IdUtils.validate(catalogId)) { - changeCatalogIds.add(catalogId); - } - } - - private synchronized Long[] getChangeCatalogIdsAndClear() { - if (changeCatalogIds.isEmpty()) { - return null; - } - Long[] catalogIds = changeCatalogIds.toArray(Long[]::new); - changeCatalogIds.clear(); - return catalogIds; - } - - private void update() { - Long[] catalogIds = getChangeCatalogIdsAndClear(); - if (Objects.nonNull(catalogIds)) { - long s = System.currentTimeMillis(); - for (Long catalogId : catalogIds) { - CmsCatalog catalog = this.catalogService.getCatalog(catalogId); - if (Objects.isNull(catalog)) { - continue; - } - boolean insert = false; - CmsCatalogContentStat stat = this.catalogContentStatMapper.selectById(catalogId); - if (Objects.isNull(stat)) { - stat = new CmsCatalogContentStat(); - stat.setCatalogId(catalogId); - stat.setSiteId(catalog.getSiteId()); - insert = true; - } - List allStatus = ContentStatus.all(); - for (String status : allStatus) { - Long total = contentService.dao().lambdaQuery() - .eq(CmsContent::getCatalogId, catalogId) - .eq(CmsContent::getStatus, status) - .count(); - stat.changeStatusTotal(status, total.intValue()); - } - if (insert) { - this.catalogContentStatMapper.insert(stat); - } else { - this.catalogContentStatMapper.updateById(stat); - } - } - log.info("Stat catalog content by status cost: " + (System.currentTimeMillis() - s) + " ms"); - } - } - - @PreDestroy - public void preDestroy() { - this.update(); - } - - @Override - public void run(String... args) throws Exception { - threadPoolTaskScheduler.schedule(this::update, new PeriodicTrigger(Duration.ofSeconds(300))); - } -} +/* + * 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.cms.stat.job; + +import com.chestnut.cms.stat.domain.CmsCatalogContentStat; +import com.chestnut.cms.stat.mapper.CmsCatalogContentStatMapper; +import com.chestnut.common.utils.IdUtils; +import com.chestnut.contentcore.domain.CmsCatalog; +import com.chestnut.contentcore.domain.CmsContent; +import com.chestnut.contentcore.fixed.dict.ContentStatus; +import com.chestnut.contentcore.service.ICatalogService; +import com.chestnut.contentcore.service.IContentService; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.stereotype.Component; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * 栏目内容数量状态分组统计,5分钟定时更新有变更的栏目数据到数据库 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class CatalogContentCountByStatus { + + private final ICatalogService catalogService; + + private final IContentService contentService; + + private final CmsCatalogContentStatMapper catalogContentStatMapper; + + private final ThreadPoolTaskScheduler threadPoolTaskScheduler; + + private static final Set changeCatalogIds = new HashSet<>(); + + public synchronized void triggerChange(Long catalogId) { + if (IdUtils.validate(catalogId)) { + changeCatalogIds.add(catalogId); + } + } + + private synchronized Long[] getChangeCatalogIdsAndClear() { + if (changeCatalogIds.isEmpty()) { + return null; + } + Long[] catalogIds = changeCatalogIds.toArray(Long[]::new); + changeCatalogIds.clear(); + return catalogIds; + } + + protected void update() { + Long[] catalogIds = getChangeCatalogIdsAndClear(); + if (Objects.nonNull(catalogIds)) { + long s = System.currentTimeMillis(); + for (Long catalogId : catalogIds) { + CmsCatalog catalog = this.catalogService.getCatalog(catalogId); + if (Objects.isNull(catalog)) { + continue; + } + boolean insert = false; + CmsCatalogContentStat stat = this.catalogContentStatMapper.selectById(catalogId); + if (Objects.isNull(stat)) { + stat = new CmsCatalogContentStat(); + stat.setCatalogId(catalogId); + stat.setSiteId(catalog.getSiteId()); + insert = true; + } + List allStatus = ContentStatus.all(); + for (String status : allStatus) { + Long total = contentService.dao().lambdaQuery() + .eq(CmsContent::getCatalogId, catalogId) + .eq(CmsContent::getStatus, status) + .count(); + stat.changeStatusTotal(status, total.intValue()); + } + if (insert) { + this.catalogContentStatMapper.insert(stat); + } else { + this.catalogContentStatMapper.updateById(stat); + } + } + log.info("Stat catalog content by status cost: " + (System.currentTimeMillis() - s) + " ms"); + } + } + + @PreDestroy + public void preDestroy() { + this.update(); + log.info("Stat catalog content by status complete. "); + } +} diff --git a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/job/StatContentCountByStatusJobHandler.java b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/job/StatContentCountByStatusJobHandler.java new file mode 100644 index 00000000..d8086ba9 --- /dev/null +++ b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/job/StatContentCountByStatusJobHandler.java @@ -0,0 +1,64 @@ +/* + * 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.cms.stat.job; + +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.springframework.stereotype.Component; + +/** + * 内容发布统计任务 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@RequiredArgsConstructor +@Component(IScheduledHandler.BEAN_PREFIX + StatContentCountByStatusJobHandler.JOB_NAME) +public class StatContentCountByStatusJobHandler extends IJobHandler implements IScheduledHandler { + + static final String JOB_NAME = "StatContentCountByStatus"; + + private final CatalogContentCountByStatus catalogContentCountByStatus; + + private final UserContentCountByStatus userContentCountByStatus; + + @Override + public String getId() { + return JOB_NAME; + } + + @Override + public String getName() { + return "{SCHEDULED_TASK." + JOB_NAME + "}"; + } + + @Override + public void exec() throws Exception { + logger.info("Job start: {}", JOB_NAME); + long s = System.currentTimeMillis(); + catalogContentCountByStatus.update(); + userContentCountByStatus.update(); + logger.info("Job '{}' completed, cost: {}ms", JOB_NAME, System.currentTimeMillis() - s); + } + + @Override + @XxlJob(JOB_NAME) + public void execute() throws Exception { + this.exec(); + } +} diff --git a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/UserContentCountByStatus.java b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/job/UserContentCountByStatus.java similarity index 79% rename from chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/UserContentCountByStatus.java rename to chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/job/UserContentCountByStatus.java index ed22b9c8..8b792b46 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/UserContentCountByStatus.java +++ b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/job/UserContentCountByStatus.java @@ -1,132 +1,134 @@ -/* - * 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.cms.stat; - -import com.chestnut.cms.stat.domain.CmsUserContentStat; -import com.chestnut.cms.stat.mapper.CmsUserContentStatMapper; -import com.chestnut.common.utils.IdUtils; -import com.chestnut.contentcore.domain.CmsContent; -import com.chestnut.contentcore.fixed.dict.ContentStatus; -import com.chestnut.contentcore.service.IContentService; -import com.chestnut.system.domain.SysUser; -import com.chestnut.system.service.ISysUserService; -import jakarta.annotation.PreDestroy; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.CommandLineRunner; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.scheduling.support.PeriodicTrigger; -import org.springframework.stereotype.Component; - -import java.time.Duration; -import java.util.*; -import java.util.stream.Collectors; - -/** - * 后台用户内容数量状态分组统计,5分钟定时更新有变更的栏目数据到数据库 - * - * @author 兮玥 - * @email 190785909@qq.com - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class UserContentCountByStatus implements CommandLineRunner { - - private final IContentService contentService; - - private final ISysUserService userService; - - private final CmsUserContentStatMapper userContentStatMapper; - - private final ThreadPoolTaskScheduler threadPoolTaskScheduler; - - /** - * > - */ - private static final Map> changes = new HashMap<>(); - - public synchronized void triggerChange(String userName, Long siteId) { - if (changes.containsKey(userName)) { - changes.get(userName).add(siteId); - } else { - Set set = new HashSet<>(); - set.add(siteId); - changes.put(userName, set); - } - } - - private synchronized Map> getChangeUserIdsAndClear() { - if (changes.isEmpty()) { - return null; - } - Map> collect = changes.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - changes.clear(); - return collect; - } - - private void update() { - Map> map = getChangeUserIdsAndClear(); - if (Objects.nonNull(map) && !map.isEmpty()) { - long s = System.currentTimeMillis(); - map.forEach((uname, siteIds) -> { - Optional opt = this.userService.lambdaQuery().eq(SysUser::getUserName, uname).oneOpt(); - if (opt.isPresent()) { - SysUser user = opt.get(); - siteIds.forEach(siteId -> { - boolean insert = false; - CmsUserContentStat stat = this.userContentStatMapper.selectById(uname); - if (Objects.isNull(stat)) { - stat = new CmsUserContentStat(); - stat.setId(IdUtils.getSnowflakeIdStr()); - stat.setUserId(user.getUserId()); - stat.setUserName(user.getUserName()); - stat.setSiteId(siteId); - insert = true; - } - List allStatus = ContentStatus.all(); - for (String status : allStatus) { - Long total = contentService.dao().lambdaQuery() - .ne(CmsContent::getContributorId, 0) - .eq(CmsContent::getSiteId, siteId) - .eq(CmsContent::getCreateBy, stat.getUserName()) - .eq(CmsContent::getStatus, status) - .count(); - stat.changeStatusTotal(status, total.intValue()); - } - if (insert) { - this.userContentStatMapper.insert(stat); - } else { - this.userContentStatMapper.updateById(stat); - } - }); - } - }); - log.info("Stat user content by status cost: " + (System.currentTimeMillis() - s) + " ms"); - } - } - - @PreDestroy - public void preDestroy() { - this.update(); - } - - @Override - public void run(String... args) throws Exception { - threadPoolTaskScheduler.schedule(this::update, new PeriodicTrigger(Duration.ofSeconds(300))); - } -} +/* + * 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.cms.stat.job; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.chestnut.cms.stat.domain.CmsUserContentStat; +import com.chestnut.cms.stat.mapper.CmsUserContentStatMapper; +import com.chestnut.common.utils.IdUtils; +import com.chestnut.common.utils.StringUtils; +import com.chestnut.contentcore.domain.CmsContent; +import com.chestnut.contentcore.fixed.dict.ContentStatus; +import com.chestnut.contentcore.service.IContentService; +import com.chestnut.system.domain.SysUser; +import com.chestnut.system.service.ISysUserService; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 后台用户内容数量状态分组统计,5分钟定时更新有变更的栏目数据到数据库 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class UserContentCountByStatus { + + private final IContentService contentService; + + private final ISysUserService userService; + + private final CmsUserContentStatMapper userContentStatMapper; + + /** + * > + */ + private static final Map> changes = new HashMap<>(); + + public synchronized void triggerChange(String userName, Long siteId) { + if (changes.containsKey(userName)) { + changes.get(userName).add(siteId); + } else { + Set set = new HashSet<>(); + set.add(siteId); + changes.put(userName, set); + } + } + + private synchronized Map> getChangeUserIdsAndClear() { + if (changes.isEmpty()) { + return null; + } + Map> collect = changes.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + changes.clear(); + return collect; + } + + protected void update() { + Map> map = getChangeUserIdsAndClear(); + if (Objects.nonNull(map) && !map.isEmpty()) { + long s = System.currentTimeMillis(); + map.forEach((uname, siteIds) -> { + Optional opt = this.userService.lambdaQuery().eq(SysUser::getUserName, uname).oneOpt(); + if (opt.isPresent()) { + SysUser user = opt.get(); + siteIds.forEach(siteId -> { + boolean insert = false; + List stats = this.userContentStatMapper + .selectList(new LambdaQueryWrapper() + .eq(CmsUserContentStat::getUserId, user.getUserId()) + .eq(CmsUserContentStat::getSiteId, siteId)); + if (stats.size() > 1) { + // 兼容下历史错误数据,直接干掉~ + this.userContentStatMapper.deleteByIds(stats.stream().map(CmsUserContentStat::getId).toList()); + } + CmsUserContentStat stat; + if (StringUtils.isEmpty(stats)) { + stat = new CmsUserContentStat(); + stat.setId(IdUtils.getSnowflakeIdStr()); + stat.setUserId(user.getUserId()); + stat.setUserName(user.getUserName()); + stat.setSiteId(siteId); + insert = true; + } else { + stat = stats.get(0); + } + List allStatus = ContentStatus.all(); + for (String status : allStatus) { + Long total = contentService.dao().lambdaQuery() + .eq(CmsContent::getContributorId, 0) + .eq(CmsContent::getSiteId, siteId) + .eq(CmsContent::getCreateBy, stat.getUserName()) + .eq(CmsContent::getStatus, status) + .count(); + stat.changeStatusTotal(status, total.intValue()); + } + if (insert) { + this.userContentStatMapper.insert(stat); + } else { + this.userContentStatMapper.updateById(stat); + } + }); + } + }); + log.info("Stat user content by status cost: " + (System.currentTimeMillis() - s) + " ms"); + } + } + + @PreDestroy + public void preDestroy() { + this.update(); + log.info("Stat user content by status complete. "); + } +} diff --git a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/listener/CmsStatEventListener.java b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/listener/CmsStatEventListener.java index 23de137a..a02a51f3 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/listener/CmsStatEventListener.java +++ b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/listener/CmsStatEventListener.java @@ -15,8 +15,8 @@ */ package com.chestnut.cms.stat.listener; -import com.chestnut.cms.stat.CatalogContentCountByStatus; -import com.chestnut.cms.stat.UserContentCountByStatus; +import com.chestnut.cms.stat.job.CatalogContentCountByStatus; +import com.chestnut.cms.stat.job.UserContentCountByStatus; import com.chestnut.common.utils.IdUtils; import com.chestnut.contentcore.domain.CmsContent; import com.chestnut.contentcore.listener.event.*; diff --git a/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages.properties b/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages.properties index 652b42a6..e1b0a083 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages.properties +++ b/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages.properties @@ -21,4 +21,6 @@ STAT.MENU.ContentStatByUser=用户发布统计 # TAG FREEMARKER.TAG.cms_stat.NAME=访问统计标签 -FREEMARKER.TAG.cms_stat.DESC=用于在模板页面中插入内置访问统计脚本代码 \ No newline at end of file +FREEMARKER.TAG.cms_stat.DESC=用于在模板页面中插入内置访问统计脚本代码 + +SCHEDULED_TASK.StatContentCountByStatus=内容发布统计任务 diff --git a/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_en.properties b/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_en.properties index 98323887..d186fd4e 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_en.properties +++ b/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_en.properties @@ -21,4 +21,6 @@ STAT.MENU.ContentStatByUser=Stat By User # TAG FREEMARKER.TAG.cms_stat.NAME=Visit Stat Tag -FREEMARKER.TAG.cms_stat.DESC=Used to insert page visit statistical script into template pages. \ No newline at end of file +FREEMARKER.TAG.cms_stat.DESC=Used to insert page visit statistical script into template pages. + +SCHEDULED_TASK.StatContentCountByStatus=Content publish stat task \ No newline at end of file diff --git a/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_zh_TW.properties b/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_zh_TW.properties index 2af865e6..85fffc87 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_zh_TW.properties +++ b/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_zh_TW.properties @@ -22,3 +22,5 @@ STAT.MENU.ContentStatByUser=用戶發布統計 # TAG FREEMARKER.TAG.cms_stat.NAME=訪問統計標籤 FREEMARKER.TAG.cms_stat.DESC=用於在模板頁面中插入內置訪問統計腳本代碼 + +SCHEDULED_TASK.StatContentCountByStatus=內容發佈統計任務 diff --git a/chestnut-cms/chestnut-cms-vote/pom.xml b/chestnut-cms/chestnut-cms-vote/pom.xml index 8b059070..f489d191 100644 --- a/chestnut-cms/chestnut-cms-vote/pom.xml +++ b/chestnut-cms/chestnut-cms-vote/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-vote diff --git a/chestnut-cms/chestnut-cms-word/pom.xml b/chestnut-cms/chestnut-cms-word/pom.xml index a97e692e..ea9edabe 100644 --- a/chestnut-cms/chestnut-cms-word/pom.xml +++ b/chestnut-cms/chestnut-cms-word/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.5 + 1.5.6 chestnut-cms-word diff --git a/chestnut-cms/chestnut-cms-word/src/main/java/com/chestnut/cms/word/properties/HotWordGroupsProperty.java b/chestnut-cms/chestnut-cms-word/src/main/java/com/chestnut/cms/word/properties/HotWordGroupsProperty.java index 0e1a34e9..fcc78068 100644 --- a/chestnut-cms/chestnut-cms-word/src/main/java/com/chestnut/cms/word/properties/HotWordGroupsProperty.java +++ b/chestnut-cms/chestnut-cms-word/src/main/java/com/chestnut/cms/word/properties/HotWordGroupsProperty.java @@ -15,15 +15,14 @@ */ package com.chestnut.cms.word.properties; -import java.util.Map; - -import org.apache.commons.collections4.MapUtils; -import org.springframework.stereotype.Component; - import com.chestnut.common.utils.JacksonUtils; import com.chestnut.common.utils.StringUtils; import com.chestnut.contentcore.core.IProperty; import com.chestnut.contentcore.util.ConfigPropertyUtils; +import org.apache.commons.collections4.MapUtils; +import org.springframework.stereotype.Component; + +import java.util.Map; /** * 应用热词分组IDs @@ -35,8 +34,9 @@ public class HotWordGroupsProperty implements IProperty { static UseType[] UseTypes = new UseType[] { UseType.Site, UseType.Catalog }; - private static final String[] DEFAULT_VALUE = {}; - + private static final String DEFAULT_VALUE = "[]"; + private static final String[] EMPTY_VALUE = {}; + @Override public UseType[] getUseTypes() { return UseTypes; @@ -53,7 +53,7 @@ public class HotWordGroupsProperty implements IProperty { } @Override - public String[] defaultValue() { + public String defaultValue() { return DEFAULT_VALUE; } @@ -63,7 +63,7 @@ public class HotWordGroupsProperty implements IProperty { if (StringUtils.isNotEmpty(string)) { return JacksonUtils.from(string, String[].class); } - return defaultValue(); + return EMPTY_VALUE; } public static String[] getHotWordGroupCodes(Map firstProps, Map secondProps) { @@ -71,6 +71,6 @@ public class HotWordGroupsProperty implements IProperty { if (StringUtils.isNotEmpty(propValue)) { return JacksonUtils.from(propValue, String[].class); } - return DEFAULT_VALUE; + return EMPTY_VALUE; } } diff --git a/chestnut-cms/chestnut-cms-word/src/main/java/com/chestnut/cms/word/template/tag/CmsTagWordGroupTag.java b/chestnut-cms/chestnut-cms-word/src/main/java/com/chestnut/cms/word/template/tag/CmsTagWordGroupTag.java index ac3b4bbd..18ce94dc 100644 --- a/chestnut-cms/chestnut-cms-word/src/main/java/com/chestnut/cms/word/template/tag/CmsTagWordGroupTag.java +++ b/chestnut-cms/chestnut-cms-word/src/main/java/com/chestnut/cms/word/template/tag/CmsTagWordGroupTag.java @@ -110,6 +110,11 @@ public class CmsTagWordGroupTag extends AbstractListTag { return DESC; } + @Override + public String supportVersion() { + return "V1.4.2+"; + } + private enum TagWordGroupTagLevel { Root(ATTR_OPTION_LEVEL_ROOT), Current(ATTR_OPTION_LEVEL_CURRENT), diff --git a/chestnut-cms/pom.xml b/chestnut-cms/pom.xml index 0b37f083..55ac2177 100644 --- a/chestnut-cms/pom.xml +++ b/chestnut-cms/pom.xml @@ -5,7 +5,7 @@ com.chestnut chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-common/chestnut-common-captcha/pom.xml b/chestnut-common/chestnut-common-captcha/pom.xml new file mode 100644 index 00000000..58aaf330 --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/pom.xml @@ -0,0 +1,34 @@ + + + + chestnut-common + com.chestnut + 1.5.6 + + 4.0.0 + + chestnut-common-captcha + + captcha + + + + com.github.penggle + kaptcha + + + javax.servlet-api + javax.servlet + + + + + + com.chestnut + chestnut-common-redis + + + + \ No newline at end of file diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaCheckResult.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaCheckResult.java new file mode 100644 index 00000000..26c48007 --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaCheckResult.java @@ -0,0 +1,63 @@ +/* + * 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.common.captcha; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * 验证码数据 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Getter +@Setter +@NoArgsConstructor +public class CaptchaCheckResult { + + /** + * 是否校验成功 + */ + private Boolean success; + + /** + * 结果自定义数据 + */ + private String data; + + public CaptchaCheckResult(boolean success) { + this.success = success; + } + + public CaptchaCheckResult(boolean success, String data) { + this.success = success; + this.data = data; + } + + public static CaptchaCheckResult success() { + return new CaptchaCheckResult(true); + } + + public static CaptchaCheckResult success(String data) { + return new CaptchaCheckResult(true, data); + } + + public static CaptchaCheckResult fail() { + return new CaptchaCheckResult(false); + } +} \ No newline at end of file diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaData.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaData.java new file mode 100644 index 00000000..8e1667ac --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaData.java @@ -0,0 +1,63 @@ +/* + * 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.common.captcha; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Properties; + +/** + * 验证码数据 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Getter +@Setter +@NoArgsConstructor +public class CaptchaData { + + /** + * 验证码类型 + */ + private String type; + + /** + * 验证码唯一标识 + */ + private String token; + + /** + * 验证码校验数据,自定义格式 + */ + private String data; + + /** + * 验证码配置 + */ + private Properties properties; + + public CaptchaData(String type) { + this.type = type; + } + + public CaptchaData(String type, String token) { + this.type = type; + this.token = token; + } +} \ No newline at end of file diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaErrorCode.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaErrorCode.java new file mode 100644 index 00000000..618129e2 --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaErrorCode.java @@ -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.common.captcha; + +import com.chestnut.common.exception.ErrorCode; + +/** + * 验证码错误码 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +public enum CaptchaErrorCode implements ErrorCode { + + /** + * 不支持的验证码类型:{0} + */ + UNSUPPORTED_CAPTCHA_TYPE, + + /** + * 生成验证码失败:{0} + */ + GENERATE_CAPTCHA_FAILED, + + /** + * 验证码已失效,请重新获取 + */ + INVALID_CAPTCHA; + + @Override + public String value() { + return "{ERR.CAPTCHA." + this.name() + "}"; + } +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaService.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaService.java new file mode 100644 index 00000000..2eaeca77 --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaService.java @@ -0,0 +1,41 @@ +/* + * 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.common.captcha; + +import com.chestnut.common.utils.Assert; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * CaptchaService + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Service +@RequiredArgsConstructor +public class CaptchaService { + + private final Map captchaTypeMap; + + public ICaptchaType getCaptchaType(String type) { + ICaptchaType captchaType = captchaTypeMap.get(ICaptchaType.BEAN_PREFIX + type); + Assert.notNull(captchaType, () -> CaptchaErrorCode.UNSUPPORTED_CAPTCHA_TYPE.exception(type)); + return captchaType; + } +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaUtils.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaUtils.java new file mode 100644 index 00000000..801c9ad9 --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/CaptchaUtils.java @@ -0,0 +1,43 @@ +/* + * 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.common.captcha; + +import java.nio.charset.StandardCharsets; + +/** + * CaptchaUtils + * + * @author 兮玥 + * @email 190785909@qq.com + */ +public class CaptchaUtils { + + public static int getEnOrChLength(String s, int fontSize) { + int enCount = 0; + int chCount = 0; + for (int i = 0; i < s.length(); i++) { + int length = String.valueOf(s.charAt(i)).getBytes(StandardCharsets.UTF_8).length; + if (length > 1) { + chCount++; + } else { + enCount++; + } + } + int chOffset = (fontSize / 2) * chCount + 5; + int enOffset = enCount * 8; + return chOffset + enOffset; + } +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/ICaptchaStorage.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/ICaptchaStorage.java new file mode 100644 index 00000000..6fc08542 --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/ICaptchaStorage.java @@ -0,0 +1,33 @@ +/* + * 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.common.captcha; + +/** + * 验证码存储策略 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +public interface ICaptchaStorage { + + String get(String key); + + void set(String key, String value, long expireSeconds); + + void delete(String key); + + boolean has(String key); +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/ICaptchaType.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/ICaptchaType.java new file mode 100644 index 00000000..f4406a03 --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/ICaptchaType.java @@ -0,0 +1,49 @@ +/* + * 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.common.captcha; + +/** + * 验证码类型 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +public interface ICaptchaType { + + String BEAN_PREFIX = "CaptchaType_"; + + String getType(); + + /** + * 创建验证码 + */ + Object create(CaptchaData captchaData); + + /** + * 校验验证码 + * + * @param captchaData 验证码校验信息 + */ + CaptchaCheckResult check(CaptchaData captchaData); + + /** + * 指定Token是否有已通过校验 + * + * @param captchaData 验证码校验信息 + * @return 结果 + */ + boolean isTokenValidated(CaptchaData captchaData); +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/config/CaptchaConfiguration.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/config/CaptchaConfiguration.java new file mode 100644 index 00000000..3cd14180 --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/config/CaptchaConfiguration.java @@ -0,0 +1,42 @@ +/* + * Copyright 2022-2025 兮玥(190785909@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.chestnut.common.captcha.config; + +import com.chestnut.common.captcha.ICaptchaStorage; +import com.chestnut.common.captcha.config.properties.CaptchaProperties; +import com.chestnut.common.captcha.storage.RedisCaptchaStorage; +import com.chestnut.common.redis.RedisCache; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * CaptchaConfig + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Configuration +@EnableConfigurationProperties(CaptchaProperties.class) +public class CaptchaConfiguration { + + @Bean + @ConditionalOnMissingBean + public ICaptchaStorage captchaStorage(RedisCache redisCache) { + return new RedisCaptchaStorage(redisCache); + } +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/config/properties/CaptchaProperties.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/config/properties/CaptchaProperties.java new file mode 100644 index 00000000..f72a8d7a --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/config/properties/CaptchaProperties.java @@ -0,0 +1,37 @@ +/* + * 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.common.captcha.config.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 验证码通用配置 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Getter +@Setter +@ConfigurationProperties(prefix = "chestnut.captcha") +public class CaptchaProperties { + + /** + * 验证码过期时间,单位:秒,默认:10分钟 + */ + private Long expireSeconds = 600L; +} diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/captcha/KaptchaTextCreator.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/KaptchaTextCreator.java similarity index 82% rename from chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/captcha/KaptchaTextCreator.java rename to chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/KaptchaTextCreator.java index dd50530d..0b9ed438 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/captcha/KaptchaTextCreator.java +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/KaptchaTextCreator.java @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.chestnut.common.captcha; - -import java.util.Random; +package com.chestnut.common.captcha.math; import com.google.code.kaptcha.text.impl.DefaultTextCreator; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.concurrent.ThreadLocalRandom; + /** * 验证码文本生成器 * @@ -31,18 +33,18 @@ public class KaptchaTextCreator extends DefaultTextCreator { @Override public String getText() { - Integer result = 0; - Random random = new Random(); + int result; + ThreadLocalRandom random = ThreadLocalRandom.current(); int x = random.nextInt(10); int y = random.nextInt(10); StringBuilder suChinese = new StringBuilder(); - int randomoperands = random.nextInt(3); - if (randomoperands == 0) { + int randomOperands = random.nextInt(3); + if (randomOperands == 0) { result = x * y; suChinese.append(CNUMBERS[x]); suChinese.append("*"); suChinese.append(CNUMBERS[y]); - } else if (randomoperands == 1) { + } else if (randomOperands == 1) { if ((x != 0) && y % x == 0) { result = y / x; suChinese.append(CNUMBERS[y]); @@ -67,7 +69,7 @@ public class KaptchaTextCreator extends DefaultTextCreator { suChinese.append(CNUMBERS[x]); } } - suChinese.append("=?@" + result); + suChinese.append("=?@").append(result); return suChinese.toString(); } } \ No newline at end of file diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/MathCaptchaCache.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/MathCaptchaCache.java new file mode 100644 index 00000000..a6e30d1c --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/MathCaptchaCache.java @@ -0,0 +1,40 @@ +/* + * 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.common.captcha.math; + +import lombok.Getter; +import lombok.Setter; + +/** + * MathCaptchaCache + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Getter +@Setter +public class MathCaptchaCache { + + /** + * 验证码 + */ + private String code; + + /** + * 验证码唯一标识 + */ + private String token; +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/MathCaptchaType.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/MathCaptchaType.java new file mode 100644 index 00000000..e8bf80d8 --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/MathCaptchaType.java @@ -0,0 +1,107 @@ +/* + * 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.common.captcha.math; + +import com.chestnut.common.captcha.*; +import com.chestnut.common.captcha.config.properties.CaptchaProperties; +import com.chestnut.common.utils.IdUtils; +import com.chestnut.common.utils.StringUtils; +import com.chestnut.common.utils.image.ImageUtils; +import jakarta.validation.Validator; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.awt.image.BufferedImage; +import java.io.IOException; + +/** + * MathCaptchaType + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Slf4j +@RequiredArgsConstructor +@Component(ICaptchaType.BEAN_PREFIX + MathCaptchaType.TYPE) +public class MathCaptchaType implements ICaptchaType { + + public final static String TYPE = "Math"; + private final static String IMAGE_TYPE_GIF = "gif"; + + private final static String CACHE_KEY_GET = "cc:captcha:math:"; // 待验证缓存 + + private final MathKaptchaProducer mathKaptchaProducer; + + private final ICaptchaStorage captchaStorage; + + protected final Validator validator; + + private final CaptchaProperties captchaProperties; + + @Override + public String getType() { + return TYPE; + } + + @Override + public Object create(CaptchaData captchaData) { + try { + String capText = mathKaptchaProducer.createText(); + String capStr = capText.substring(0, capText.lastIndexOf("@")); + String code = capText.substring(capText.lastIndexOf("@") + 1); + BufferedImage image = mathKaptchaProducer.createImage(capStr); + String imageBase64Str = ImageUtils.imageToBase64(image, IMAGE_TYPE_GIF); + String token = captchaData.getToken(); + if (StringUtils.isEmpty(token)) { + token = IdUtils.simpleUUID(); + } + // 写入缓存 + captchaStorage.set(CACHE_KEY_GET + token, code, captchaProperties.getExpireSeconds()); + // 返回客户端 + return new MathCaptchaVO(token, imageBase64Str); + } catch (IOException e) { + throw CaptchaErrorCode.GENERATE_CAPTCHA_FAILED.exception(TYPE); + } + } + + @Override + public CaptchaCheckResult check(CaptchaData captchaData) { + try { + if (!validate(captchaData.getToken(), captchaData.getData())) { + return CaptchaCheckResult.fail(); + } + return CaptchaCheckResult.success(); + } finally { + this.captchaStorage.delete(CACHE_KEY_GET + captchaData.getToken()); + } + } + + private boolean validate(String token, String code) { + String cacheKey = CACHE_KEY_GET + token; + if (!this.captchaStorage.has(cacheKey)) { + return false; + } + String cacheCode = this.captchaStorage.get(cacheKey); + return cacheCode.equals(code); + } + + @Override + public boolean isTokenValidated(CaptchaData captchaData) { + return validate(captchaData.getToken(), captchaData.getData()); + } +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/MathCaptchaVO.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/MathCaptchaVO.java new file mode 100644 index 00000000..58bf4df3 --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/MathCaptchaVO.java @@ -0,0 +1,28 @@ +/* + * 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.common.captcha.math; + +/** + * MathCaptcha + * + * @author 兮玥 + * @email 190785909@qq.com + */ +public record MathCaptchaVO( + String token, // 验证码唯一标识 + String image // 验证码图片 +) { +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/MathKaptchaProducer.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/MathKaptchaProducer.java new file mode 100644 index 00000000..eb5104b2 --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/math/MathKaptchaProducer.java @@ -0,0 +1,74 @@ +/* + * 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.common.captcha.math; + +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.util.Properties; + +import static com.google.code.kaptcha.Constants.*; + +/** + * MathKaptcha + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Component +public class MathKaptchaProducer extends DefaultKaptcha implements CommandLineRunner { + + private static final String KaptchaTextCreator = "com.chestnut.common.captcha.math.KaptchaTextCreator"; + + @Override + public void run(String... args) throws Exception { + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, KaptchaTextCreator); + // 验证码文本字符间距 默认为2 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple + // 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy + // 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + this.setConfig(config); + } +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/storage/RedisCaptchaStorage.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/storage/RedisCaptchaStorage.java new file mode 100644 index 00000000..f8b947c3 --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/storage/RedisCaptchaStorage.java @@ -0,0 +1,54 @@ +/* + * 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.common.captcha.storage; + +import com.chestnut.common.captcha.ICaptchaStorage; +import com.chestnut.common.redis.RedisCache; +import lombok.RequiredArgsConstructor; + +import java.util.concurrent.TimeUnit; + +/** + * RedisCaptchaStorage + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@RequiredArgsConstructor +public class RedisCaptchaStorage implements ICaptchaStorage { + + private final RedisCache redisCache; + + @Override + public String get(String key) { + return redisCache.getCacheObject(key, String.class); + } + + @Override + public void set(String key, String value, long expireSeconds) { + redisCache.setCacheObject(key, value, expireSeconds, TimeUnit.SECONDS); + } + + @Override + public void delete(String key) { + redisCache.deleteObject(key); + } + + @Override + public boolean has(String key) { + return redisCache.hasKey(key); + } +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/text/TextCaptchaType.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/text/TextCaptchaType.java new file mode 100644 index 00000000..6bcd346f --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/text/TextCaptchaType.java @@ -0,0 +1,104 @@ +/* + * 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.common.captcha.text; + +import com.chestnut.common.captcha.*; +import com.chestnut.common.captcha.config.properties.CaptchaProperties; +import com.chestnut.common.utils.IdUtils; +import com.chestnut.common.utils.StringUtils; +import com.chestnut.common.utils.image.ImageUtils; +import jakarta.validation.Validator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.awt.image.BufferedImage; +import java.io.IOException; + +/** + * TextCaptchaType + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Slf4j +@RequiredArgsConstructor +@Component(ICaptchaType.BEAN_PREFIX + TextCaptchaType.TYPE) +public class TextCaptchaType implements ICaptchaType { + + public final static String TYPE = "Text"; + private final static String IMAGE_TYPE_GIF = "gif"; + + private final static String CACHE_KEY_GET = "cc:captcha:text:"; // 待验证缓存 + + private final TextKaptchaProducer textKaptchaProducer; + + private final ICaptchaStorage captchaStorage; + + protected final Validator validator; + + private final CaptchaProperties captchaProperties; + + @Override + public String getType() { + return TYPE; + } + + @Override + public Object create(CaptchaData captchaData) { + try { + String capStr = textKaptchaProducer.createText(); + BufferedImage image = textKaptchaProducer.createImage(capStr); + String imageBase64Str = ImageUtils.imageToBase64(image, IMAGE_TYPE_GIF); + String token = captchaData.getToken(); + if (StringUtils.isEmpty(token)) { + token = IdUtils.simpleUUID(); + } + // 写入缓存 + captchaStorage.set(CACHE_KEY_GET + token, capStr, captchaProperties.getExpireSeconds()); + // 返回客户端 + return new TextCaptchaVO(token, imageBase64Str); + } catch (IOException e) { + throw CaptchaErrorCode.GENERATE_CAPTCHA_FAILED.exception(TYPE); + } + } + + @Override + public CaptchaCheckResult check(CaptchaData captchaData) { + try { + if (!validate(captchaData.getToken(), captchaData.getData())) { + return CaptchaCheckResult.fail(); + } + return CaptchaCheckResult.success(); + } finally { + this.captchaStorage.delete(CACHE_KEY_GET + captchaData.getToken()); + } + } + + private boolean validate(String token, String code) { + String cacheKey = CACHE_KEY_GET + token; + if (!this.captchaStorage.has(cacheKey)) { + return false; + } + String cacheCode = this.captchaStorage.get(cacheKey); + return cacheCode.equals(code); + } + + @Override + public boolean isTokenValidated(CaptchaData captchaData) { + return validate(captchaData.getToken(), captchaData.getData()); + } +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/text/TextCaptchaVO.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/text/TextCaptchaVO.java new file mode 100644 index 00000000..cfb9e037 --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/text/TextCaptchaVO.java @@ -0,0 +1,28 @@ +/* + * 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.common.captcha.text; + +/** + * TextCaptchaVO + * + * @author 兮玥 + * @email 190785909@qq.com + */ +public record TextCaptchaVO( + String token, // 验证码唯一标识 + String image // 验证码图片 +) { +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/text/TextKaptchaProducer.java b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/text/TextKaptchaProducer.java new file mode 100644 index 00000000..1223c00d --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/java/com/chestnut/common/captcha/text/TextKaptchaProducer.java @@ -0,0 +1,65 @@ +/* + * 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.common.captcha.text; + +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.util.Properties; + +import static com.google.code.kaptcha.Constants.*; + +/** + * MathKaptcha + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Component +public class TextKaptchaProducer extends DefaultKaptcha implements CommandLineRunner { + + private static final String KaptchaTextCreator = "com.chestnut.common.captcha.math.KaptchaTextCreator"; + + @Override + public void run(String... args) throws Exception { + + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple + // 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy + // 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + this.setConfig(config); + } +} diff --git a/chestnut-common/chestnut-common-captcha/src/main/resources/i18n/messages.properties b/chestnut-common/chestnut-common-captcha/src/main/resources/i18n/messages.properties new file mode 100644 index 00000000..16a4c21f --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/resources/i18n/messages.properties @@ -0,0 +1,4 @@ +#错误消息 +ERR.CAPTCHA.UNSUPPORTED_CAPTCHA_TYPE=不支持的验证码类型:{0} +ERR.CAPTCHA.GENERATE_CAPTCHA_FAILED=生成验证码失败! +ERR.CAPTCHA.INVALID_CAPTCHA=验证码已失效,请重新获取! diff --git a/chestnut-common/chestnut-common-captcha/src/main/resources/i18n/messages_en.properties b/chestnut-common/chestnut-common-captcha/src/main/resources/i18n/messages_en.properties new file mode 100644 index 00000000..caac9a8f --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/resources/i18n/messages_en.properties @@ -0,0 +1,4 @@ +#错误消息 +ERR.CAPTCHA.UNSUPPORTED_CAPTCHA_TYPE=Unsupported captcha type: {0} +ERR.CAPTCHA.GENERATE_CAPTCHA_FAILED=Generate captcha fail! +ERR.CAPTCHA.INVALID_CAPTCHA=The captcha expired, please try again! diff --git a/chestnut-common/chestnut-common-captcha/src/main/resources/i18n/messages_zh_TW.properties b/chestnut-common/chestnut-common-captcha/src/main/resources/i18n/messages_zh_TW.properties new file mode 100644 index 00000000..6ffef86d --- /dev/null +++ b/chestnut-common/chestnut-common-captcha/src/main/resources/i18n/messages_zh_TW.properties @@ -0,0 +1,4 @@ +#错误消息 +ERR.CAPTCHA.UNSUPPORTED_CAPTCHA_TYPE=不支持的驗證碼類型:{0} +ERR.CAPTCHA.GENERATE_CAPTCHA_FAILED=生成驗證碼失敗! +ERR.CAPTCHA.INVALID_CAPTCHA=驗證碼已失效,請重新獲取! diff --git a/chestnut-common/chestnut-common-core/pom.xml b/chestnut-common/chestnut-common-core/pom.xml index f420ba07..fd56f28a 100644 --- a/chestnut-common/chestnut-common-core/pom.xml +++ b/chestnut-common/chestnut-common-core/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.5 + 1.5.6 4.0.0 @@ -63,20 +63,8 @@ - com.alibaba - easyexcel - - - - - com.github.penggle - kaptcha - - - javax.servlet-api - javax.servlet - - + cn.idev.excel + fastexcel diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/config/AsyncConfig.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/config/AsyncConfig.java index 5b3b5f09..fd26c0aa 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/config/AsyncConfig.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/config/AsyncConfig.java @@ -17,6 +17,7 @@ package com.chestnut.common.config; import com.chestnut.common.config.properties.AsyncProperties; import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -47,6 +48,7 @@ public class AsyncConfig implements AsyncConfigurer { private final AsyncProperties properties; + @NotNull @Bean(COMMON_EXECUTOR_BEAN) @Override public Executor getAsyncExecutor() { diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/config/CaptchaConfig.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/config/CaptchaConfig.java deleted file mode 100644 index cc8e5603..00000000 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/config/CaptchaConfig.java +++ /dev/null @@ -1,122 +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.common.config; - -import static com.google.code.kaptcha.Constants.KAPTCHA_BORDER; -import static com.google.code.kaptcha.Constants.KAPTCHA_BORDER_COLOR; -import static com.google.code.kaptcha.Constants.KAPTCHA_IMAGE_HEIGHT; -import static com.google.code.kaptcha.Constants.KAPTCHA_IMAGE_WIDTH; -import static com.google.code.kaptcha.Constants.KAPTCHA_NOISE_COLOR; -import static com.google.code.kaptcha.Constants.KAPTCHA_NOISE_IMPL; -import static com.google.code.kaptcha.Constants.KAPTCHA_OBSCURIFICATOR_IMPL; -import static com.google.code.kaptcha.Constants.KAPTCHA_SESSION_CONFIG_KEY; -import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH; -import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE; -import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR; -import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES; -import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE; -import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_IMPL; - -import java.util.Properties; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import com.google.code.kaptcha.impl.DefaultKaptcha; -import com.google.code.kaptcha.util.Config; - -/** - * 验证码配置 - * 验证码类型 math 数组计算 char 字符验证 - * - * @author 兮玥 - * @email 190785909@qq.com - */ -@Configuration -public class CaptchaConfig { - - private static final String KaptchaTextCreator = "com.chestnut.common.captcha.KaptchaTextCreator"; - - public static final String BEAN_PREFIX = "Kaptcha_"; - - @Bean(BEAN_PREFIX + "char") - public DefaultKaptcha getKaptchaBean() { - DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); - Properties properties = new Properties(); - // 是否有边框 默认为true 我们可以自己设置yes,no - properties.setProperty(KAPTCHA_BORDER, "yes"); - // 验证码文本字符颜色 默认为Color.BLACK - properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); - // 验证码图片宽度 默认为200 - properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); - // 验证码图片高度 默认为50 - properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); - // 验证码文本字符大小 默认为40 - properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); - // KAPTCHA_SESSION_KEY - properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); - // 验证码文本字符长度 默认为5 - properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); - // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) - properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); - // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple - // 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy - // 阴影com.google.code.kaptcha.impl.ShadowGimpy - properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); - Config config = new Config(properties); - defaultKaptcha.setConfig(config); - return defaultKaptcha; - } - - @Bean(BEAN_PREFIX + "math") - public DefaultKaptcha getKaptchaBeanMath() { - DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); - Properties properties = new Properties(); - // 是否有边框 默认为true 我们可以自己设置yes,no - properties.setProperty(KAPTCHA_BORDER, "yes"); - // 边框颜色 默认为Color.BLACK - properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); - // 验证码文本字符颜色 默认为Color.BLACK - properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); - // 验证码图片宽度 默认为200 - properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); - // 验证码图片高度 默认为50 - properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); - // 验证码文本字符大小 默认为40 - properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); - // KAPTCHA_SESSION_KEY - properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); - // 验证码文本生成器 - properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, KaptchaTextCreator); - // 验证码文本字符间距 默认为2 - properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); - // 验证码文本字符长度 默认为5 - properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); - // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) - properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); - // 验证码噪点颜色 默认为Color.BLACK - properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); - // 干扰实现类 - properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); - // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple - // 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy - // 阴影com.google.code.kaptcha.impl.ShadowGimpy - properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); - Config config = new Config(properties); - defaultKaptcha.setConfig(config); - return defaultKaptcha; - } -} diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/i18n/I18nUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/i18n/I18nUtils.java index 86e703ac..2ffc4172 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/i18n/I18nUtils.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/i18n/I18nUtils.java @@ -101,27 +101,33 @@ public class I18nUtils { } /** - * 获取国际化键名对应的当前默认语言值 + * 解析字符串中的国际化占位符转换成当前语言环境的语言值 * - * @param str - * @return + * @param str 带国际化占位符{xxx}的字符串 + * @return 结果 */ public static String get(String str) { return get(str, LocaleContextHolder.getLocale()); } - + /** + * 解析字符串中的国际化占位符转换成当前语言环境的语言值 + * + * @param str 带国际化占位符{xxx}的字符串 + * @param args 参数 + * @return 结果 + */ public static String get(String str, Object... args) { return get(str, LocaleContextHolder.getLocale(), args); } /** - * 获取国际化键名指定的语言值 + * 解析字符串中的国际化占位符转换成指定的语言值 * - * @param str - * @param locale - * @param args - * @return + * @param str 带国际化占位符{xxx}的字符串 + * @param locale 语言区域 + * @param args 参数 + * @return 结果 */ public static String get(String str, Locale locale, Object... args) { if (StringUtils.isEmpty(str)) { @@ -130,6 +136,42 @@ public class I18nUtils { return PlaceholderHelper.replacePlaceholders(str, langKey -> messageSource.getMessage(langKey, args, locale)); } + /** + * 获取国际化键名指定的语言值 + * + * @param langKey 国际化键名 + * @return 结果 + */ + public static String parse(String langKey) { + return parse(langKey, LocaleContextHolder.getLocale()); + } + + /** + * 获取国际化键名指定的语言值 + * + * @param langKey 国际化键名 + * @param args 参数 + * @return 结果 + */ + public static String parse(String langKey, Object... args) { + return parse(langKey, LocaleContextHolder.getLocale(), args); + } + + /** + * 获取国际化键名指定的语言值 + * + * @param langKey 国际化键名 + * @param locale 语言区域 + * @param args 参数 + * @return 结果 + */ + public static String parse(String langKey, Locale locale, Object... args) { + if (StringUtils.isEmpty(langKey)) { + return langKey; + } + return messageSource.getMessage(langKey, args, locale); + } + public static boolean isLanguageTag(String s) { int len = s.length(); return (len >= 2) && (len <= 8) && isAlphaString(s); diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/serializer/DesensitizationJsonSerializer.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/serializer/DesensitizationJsonSerializer.java new file mode 100644 index 00000000..d212567e --- /dev/null +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/serializer/DesensitizationJsonSerializer.java @@ -0,0 +1,43 @@ +/* + * 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.common.serializer; + +import com.chestnut.common.utils.StringUtils; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * 字段脱敏序列化处理 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +public class DesensitizationJsonSerializer extends JsonSerializer { + + private static final String MASKER = "******"; + + @Override + public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException { + if (StringUtils.isNotEmpty(value)) { + gen.writeString(MASKER); + return; + } + gen.writeString(value); + } +} \ No newline at end of file diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/IdUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/IdUtils.java index 38f63a1f..47151cb5 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/IdUtils.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/IdUtils.java @@ -61,10 +61,6 @@ public class IdUtils { return UUID.randomUUID().toString(); } - public static void main(String[] args) { - System.out.println(UUID.randomUUID().toString()); - } - /** * 简化的UUID,去掉了横线 * diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/StringUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/StringUtils.java index 579ac0cd..1e3f2f6d 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/StringUtils.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/StringUtils.java @@ -709,10 +709,10 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { * @return */ public static String replaceEx(String str, String searchStr, String replacement) { - if (str == null || str.length() == 0 || replacement == null) { + if (str == null || str.isEmpty() || replacement == null) { return str; } - if (searchStr == null || searchStr.length() == 0 || searchStr.length() > str.length()) { + if (searchStr == null || searchStr.isEmpty() || searchStr.length() > str.length()) { return str; } StringBuilder sb = null; @@ -725,7 +725,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { if (sb == null) { sb = new StringBuilder(); } - sb.append(str.substring(lastIndex, index)); + sb.append(str, lastIndex, index); sb.append(replacement); } lastIndex = index + searchStr.length(); @@ -783,4 +783,33 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { } return str; } + + public static boolean isSpace(char c) { + return (c == ' ') || (c == ' ') || (c == '\r') || (c == '\n') + || (c == '\t') || (c == '\b') || (c == '\f'); + } + + public static String leftTrim(String str) { + if (isEmpty(str)) { + return str; + } + for (int i = 0; i < str.length(); i++) { + if (!isSpace(str.charAt(i))) { + return str.substring(i); + } + } + return str; + } + + public static String rightTrim(String str) { + if (isEmpty(str)) { + return str; + } + for (int i = str.length() - 1; i >= 0; i--) { + if (!isSpace(str.charAt(i))) { + return str.substring(0, i + 1); + } + } + return str; + } } \ No newline at end of file diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/XCollectionUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/XCollectionUtils.java new file mode 100644 index 00000000..4431dc0d --- /dev/null +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/XCollectionUtils.java @@ -0,0 +1,71 @@ +/* + * 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.common.utils; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * XCollectionUtils + * + * @author 兮玥 + * @email 190785909@qq.com + */ +public class XCollectionUtils { + + /** + * Creates a string from all the elements separated using [separator]. + */ + public static String join(Collection collection, String separator, Function func) { + return join(collection, separator, func); + } + + /** + * Creates a string from all the elements separated using [separator]. + */ + public static String join(Collection collection, String separator, Function func, Predicate predicate) { + StringBuilder sb = new StringBuilder(); + for (T item : collection) { + if (predicate.test(item)) { + if (!sb.isEmpty()) { + sb.append(separator); + } + sb.append(func.apply(item)); + } + } + return sb.toString(); + } + + /** + * Returns a [ImmutableMap] containing key-value pairs provided by [keyGetter] function and valueGetter function + * applied to elements of the given collection. + * + * If any of two pairs would have the same key the last one gets added to the map. + * + * The returned map preserves the entry iteration order of the original collection. + */ + public static Map associate(Collection collection, Function keyGetter, Function valueGetter) { + Map map = new HashMap<>(collection.size()); + for (T item : collection) { + map.put(keyGetter.apply(item), valueGetter.apply(item)); + } + return Collections.unmodifiableMap(map); + } +} diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/file/FileExUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/file/FileExUtils.java index 87de0578..6fc3702a 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/file/FileExUtils.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/file/FileExUtils.java @@ -135,8 +135,6 @@ public class FileExUtils { return ext; } } - } - if (path.contains("?")) { path = StringUtils.substringBefore(path, "?"); } return FilenameUtils.getExtension(path); @@ -220,15 +218,16 @@ public class FileExUtils { * @return 格式化路径 */ public static String normalizePath(String path) { + if (StringUtils.isEmpty(path)) { + return path; + } path = path.replace('\\', '/'); - - path = StringUtils.replaceEx(path, "../", "/"); - path = StringUtils.replaceEx(path, "./", "/"); + path = path.replaceAll("\\.+(?=/)", "/"); + path = path.replaceAll("/+", "/"); path = StringUtils.replaceEx(path, "~/", "/"); if (path.endsWith("..")) { path = path.substring(0, path.length() - 2); } - path = path.replaceAll("/+", "/"); return path; } diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/image/ImageUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/image/ImageUtils.java index 1465d229..975ac8f1 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/image/ImageUtils.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/image/ImageUtils.java @@ -26,9 +26,7 @@ import org.springframework.util.ResourceUtils; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.util.List; import java.util.*; @@ -168,4 +166,20 @@ public class ImageUtils { } return isBase64Image(v.toString()); } + + public static BufferedImage base64ToImage(String base64Str) throws IOException { + Base64.Decoder decoder = Base64.getDecoder(); + byte[] bytes = decoder.decode(base64Str); + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) { + return ImageIO.read(inputStream); + } + } + + public static String imageToBase64(BufferedImage templateImage, String imageFormat) throws IOException { + Base64.Encoder encoder = Base64.getEncoder(); + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + ImageIO.write(templateImage, imageFormat, os); + return encoder.encodeToString(os.toByteArray()).trim(); + } + } } \ No newline at end of file diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/poi/converter/LocalDateTimeConverter.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/poi/converter/LocalDateTimeConverter.java index d272f3bf..0ff69896 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/poi/converter/LocalDateTimeConverter.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/poi/converter/LocalDateTimeConverter.java @@ -15,17 +15,17 @@ */ package com.chestnut.common.utils.poi.converter; +import cn.idev.excel.converters.Converter; +import cn.idev.excel.enums.CellDataTypeEnum; +import cn.idev.excel.metadata.GlobalConfiguration; +import cn.idev.excel.metadata.data.ReadCellData; +import cn.idev.excel.metadata.data.WriteCellData; +import cn.idev.excel.metadata.property.ExcelContentProperty; +import com.chestnut.common.utils.DateUtils; + import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import com.alibaba.excel.converters.Converter; -import com.alibaba.excel.enums.CellDataTypeEnum; -import com.alibaba.excel.metadata.GlobalConfiguration; -import com.alibaba.excel.metadata.data.ReadCellData; -import com.alibaba.excel.metadata.data.WriteCellData; -import com.alibaba.excel.metadata.property.ExcelContentProperty; -import com.chestnut.common.utils.DateUtils; - public class LocalDateTimeConverter implements Converter { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DateUtils.YYYY_MM_DD_HH_MM_SS); @@ -42,13 +42,13 @@ public class LocalDateTimeConverter implements Converter { @Override public WriteCellData convertToExcelData(LocalDateTime value, ExcelContentProperty contentProperty, - GlobalConfiguration globalConfiguration) throws Exception { + GlobalConfiguration globalConfiguration) throws Exception { return new WriteCellData<>(FORMATTER.format(value)); } @Override public LocalDateTime convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, - GlobalConfiguration globalConfiguration) throws Exception { + GlobalConfiguration globalConfiguration) throws Exception { return LocalDateTime.parse(cellData.getStringValue(), FORMATTER); } } diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/poi/converter/MapToJSONStringConverter.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/poi/converter/MapToJSONStringConverter.java index 1f102c36..0121bce6 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/poi/converter/MapToJSONStringConverter.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/poi/converter/MapToJSONStringConverter.java @@ -17,12 +17,12 @@ package com.chestnut.common.utils.poi.converter; import java.util.Map; -import com.alibaba.excel.converters.Converter; -import com.alibaba.excel.enums.CellDataTypeEnum; -import com.alibaba.excel.metadata.GlobalConfiguration; -import com.alibaba.excel.metadata.data.ReadCellData; -import com.alibaba.excel.metadata.data.WriteCellData; -import com.alibaba.excel.metadata.property.ExcelContentProperty; +import cn.idev.excel.converters.Converter; +import cn.idev.excel.enums.CellDataTypeEnum; +import cn.idev.excel.metadata.GlobalConfiguration; +import cn.idev.excel.metadata.data.ReadCellData; +import cn.idev.excel.metadata.data.WriteCellData; +import cn.idev.excel.metadata.property.ExcelContentProperty; import com.chestnut.common.utils.JacksonUtils; public class MapToJSONStringConverter implements Converter> { diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/poi/converter/StringToSetConverter.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/poi/converter/StringToSetConverter.java index e88f5482..f04b1757 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/poi/converter/StringToSetConverter.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/poi/converter/StringToSetConverter.java @@ -19,12 +19,12 @@ import java.lang.reflect.Array; import java.util.Objects; import java.util.Set; -import com.alibaba.excel.converters.Converter; -import com.alibaba.excel.enums.CellDataTypeEnum; -import com.alibaba.excel.metadata.GlobalConfiguration; -import com.alibaba.excel.metadata.data.ReadCellData; -import com.alibaba.excel.metadata.data.WriteCellData; -import com.alibaba.excel.metadata.property.ExcelContentProperty; +import cn.idev.excel.converters.Converter; +import cn.idev.excel.enums.CellDataTypeEnum; +import cn.idev.excel.metadata.GlobalConfiguration; +import cn.idev.excel.metadata.data.ReadCellData; +import cn.idev.excel.metadata.data.WriteCellData; +import cn.idev.excel.metadata.property.ExcelContentProperty; import com.chestnut.common.utils.StringUtils; public class StringToSetConverter implements Converter> { diff --git a/chestnut-common/chestnut-common-datasource/pom.xml b/chestnut-common/chestnut-common-datasource/pom.xml index 1e3980cb..c368e77d 100644 --- a/chestnut-common/chestnut-common-datasource/pom.xml +++ b/chestnut-common/chestnut-common-datasource/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.5 + 1.5.6 4.0.0 @@ -23,6 +23,10 @@ com.baomidou mybatis-plus-spring-boot3-starter + + com.baomidou + mybatis-plus-jsqlparser + com.baomidou diff --git a/chestnut-common/chestnut-common-datasource/src/main/java/com/chestnut/common/db/domain/BaseEntity.java b/chestnut-common/chestnut-common-datasource/src/main/java/com/chestnut/common/db/domain/BaseEntity.java index 76489ea5..fb560668 100644 --- a/chestnut-common/chestnut-common-datasource/src/main/java/com/chestnut/common/db/domain/BaseEntity.java +++ b/chestnut-common/chestnut-common-datasource/src/main/java/com/chestnut/common/db/domain/BaseEntity.java @@ -15,9 +15,9 @@ */ package com.chestnut.common.db.domain; -import com.alibaba.excel.annotation.ExcelIgnore; -import com.alibaba.excel.annotation.ExcelProperty; -import com.alibaba.excel.annotation.write.style.ColumnWidth; +import cn.idev.excel.annotation.ExcelIgnore; +import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.write.style.ColumnWidth; import com.baomidou.mybatisplus.annotation.TableField; import com.chestnut.common.utils.poi.converter.LocalDateTimeConverter; import com.fasterxml.jackson.annotation.JsonFormat; diff --git a/chestnut-common/chestnut-common-datasource/src/main/java/com/chestnut/common/db/mybatisplus/BackupServiceImpl.java b/chestnut-common/chestnut-common-datasource/src/main/java/com/chestnut/common/db/mybatisplus/BackupServiceImpl.java index c240eb77..bed81b5c 100644 --- a/chestnut-common/chestnut-common-datasource/src/main/java/com/chestnut/common/db/mybatisplus/BackupServiceImpl.java +++ b/chestnut-common/chestnut-common-datasource/src/main/java/com/chestnut/common/db/mybatisplus/BackupServiceImpl.java @@ -86,7 +86,9 @@ public class BackupServiceImpl, T extends IBackupable @Transactional(rollbackFor = Exception.class) public void deleteAndBackup(Wrapper wrapper, String operator, String backupRemark) { List list = this.list(wrapper); - list.forEach(entity -> this.backup(entity, operator, backupRemark)); + for (T entity : list) { + this.backup(entity, operator, backupRemark); + } this.remove(wrapper); } diff --git a/chestnut-common/chestnut-common-extend/pom.xml b/chestnut-common/chestnut-common-extend/pom.xml index 98668ca6..dff6a809 100644 --- a/chestnut-common/chestnut-common-extend/pom.xml +++ b/chestnut-common/chestnut-common-extend/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-common/chestnut-common-log/pom.xml b/chestnut-common/chestnut-common-log/pom.xml index de5398f9..69234097 100644 --- a/chestnut-common/chestnut-common-log/pom.xml +++ b/chestnut-common/chestnut-common-log/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-common/chestnut-common-redis/pom.xml b/chestnut-common/chestnut-common-redis/pom.xml index e72a74fd..c99b198f 100644 --- a/chestnut-common/chestnut-common-redis/pom.xml +++ b/chestnut-common/chestnut-common-redis/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-common/chestnut-common-redis/src/main/java/com/chestnut/common/redis/RedisCache.java b/chestnut-common/chestnut-common-redis/src/main/java/com/chestnut/common/redis/RedisCache.java index 4c8f461e..78426e3d 100644 --- a/chestnut-common/chestnut-common-redis/src/main/java/com/chestnut/common/redis/RedisCache.java +++ b/chestnut-common/chestnut-common-redis/src/main/java/com/chestnut/common/redis/RedisCache.java @@ -326,6 +326,22 @@ public class RedisCache { this.redisTemplate.opsForSet().remove(key, values); } + public T randomSetValue(final String key, Class clazz) { + if (!this.hasKey(key)) { + return null; + } + Object value = this.redisTemplate.opsForSet().randomMember(key); + return clazz.cast(value); + } + + public List randomSetValues(final String key, long count, Class clazz) { + List objects = this.redisTemplate.opsForSet().randomMembers(key, count); + if (Objects.isNull(objects) || objects.isEmpty()) { + return List.of(); + } + return objects.stream().map(clazz::cast).toList(); + } + /** * Set map cache * diff --git a/chestnut-common/chestnut-common-security/pom.xml b/chestnut-common/chestnut-common-security/pom.xml index da2ef4af..230b9125 100644 --- a/chestnut-common/chestnut-common-security/pom.xml +++ b/chestnut-common/chestnut-common-security/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/SaTokenDaoRedisImpl.java b/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/SaTokenDaoRedisImpl.java index a1bec351..fd2b494f 100644 --- a/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/SaTokenDaoRedisImpl.java +++ b/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/SaTokenDaoRedisImpl.java @@ -16,6 +16,8 @@ package com.chestnut.common.security; import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.strategy.SaStrategy; import cn.dev33.satoken.util.SaFoxUtil; import com.chestnut.common.redis.RedisCache; import lombok.RequiredArgsConstructor; @@ -80,7 +82,12 @@ public class SaTokenDaoRedisImpl implements SaTokenDao { @Override public Object getObject(String key) { - return this.redisCache.getCacheObject(key, Object.class); + return this.getObject(key, Object.class); + } + + @Override + public T getObject(String key, Class clazz) { + return this.redisCache.getCacheObject(key, clazz); } @Override @@ -107,7 +114,6 @@ public class SaTokenDaoRedisImpl implements SaTokenDao { @Override public void deleteObject(String key) { this.redisCache.deleteObject(key); - } @Override @@ -127,6 +133,47 @@ public class SaTokenDaoRedisImpl implements SaTokenDao { } } + @Override + public SaSession getSession(String key) { + return this.redisCache.getCacheObject(key, SaStrategy.instance.sessionClassType); + } + + @Override + public void setSession(SaSession session, long timeout) { + if (timeout == NEVER_EXPIRE) { + this.redisCache.setCacheObject(session.getId(), session); + } else { + this.redisCache.setCacheObject(session.getId(), session, timeout, TimeUnit.SECONDS); + } + } + + @Override + public void deleteSession(String key) { + this.redisCache.deleteObject(key); + } + + @Override + public void updateSession(SaSession session) { + this.redisCache.setCacheObject(session.getId(), session); + } + + @Override + public long getSessionTimeout(String key) { + return redisCache.getExpire(key, TimeUnit.SECONDS); + } + + @Override + public void updateSessionTimeout(String key, long timeout) { + if (timeout > 0) { + this.redisCache.expire(key, timeout); + } else if (timeout == NEVER_EXPIRE) { + long expire = this.getObjectTimeout(key); + if (expire != NEVER_EXPIRE) { + this.redisCache.expire(key, timeout, TimeUnit.SECONDS); + } + } + } + @Override public List searchData(String prefix, String keyword, int start, int size, boolean sortType) { List keys = this.redisCache.scanKeys(prefix + "*" + keyword + "*", 1000).stream().toList(); diff --git a/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/aspectj/ExcelExportAspect.java b/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/aspectj/ExcelExportAspect.java index e23e9ca8..a1e16454 100644 --- a/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/aspectj/ExcelExportAspect.java +++ b/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/aspectj/ExcelExportAspect.java @@ -15,9 +15,9 @@ */ package com.chestnut.common.security.aspectj; -import com.alibaba.excel.EasyExcel; -import com.alibaba.excel.ExcelWriter; -import com.alibaba.excel.write.metadata.WriteSheet; +import cn.idev.excel.EasyExcel; +import cn.idev.excel.ExcelWriter; +import cn.idev.excel.write.metadata.WriteSheet; import com.chestnut.common.domain.R; import com.chestnut.common.security.anno.ExcelExportable; import com.chestnut.common.security.web.TableData; diff --git a/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/aspectj/SaCheckAspect.java b/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/aspectj/SaCheckAspect.java index 3f4eefcd..629d3b3c 100644 --- a/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/aspectj/SaCheckAspect.java +++ b/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/aspectj/SaCheckAspect.java @@ -17,9 +17,7 @@ package com.chestnut.common.security.aspectj; import cn.dev33.satoken.SaManager; import cn.dev33.satoken.annotation.*; -import cn.dev33.satoken.basic.SaBasicUtil; import cn.dev33.satoken.stp.StpLogic; -import cn.dev33.satoken.strategy.SaStrategy; import com.chestnut.common.security.IUserType; import com.chestnut.common.security.SecurityService; import com.chestnut.common.security.SecurityUtils; @@ -40,6 +38,7 @@ import org.springframework.stereotype.Component; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.Objects; /** * SA-TOKEN认证切面 @@ -67,8 +66,6 @@ public class SaCheckAspect { " || @annotation(cn.dev33.satoken.annotation.SaCheckSafe)" + " || @within(cn.dev33.satoken.annotation.SaCheckDisable)" + " || @annotation(cn.dev33.satoken.annotation.SaCheckDisable)" + - " || @within(cn.dev33.satoken.annotation.SaCheckBasic)" + - " || @annotation(cn.dev33.satoken.annotation.SaCheckBasic)" + " || @within(com.chestnut.common.security.anno.Priv)" + " || @annotation(com.chestnut.common.security.anno.Priv)"; @@ -84,7 +81,7 @@ public class SaCheckAspect { public Object around(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); - if (!(Boolean) SaStrategy.instance.isAnnotationPresent.apply(method, SaIgnore.class)) { + if (!method.isAnnotationPresent(SaIgnore.class)) { // 先校验 Method 所属 Class 上的注解 this.checkPermission(method.getDeclaringClass(), joinPoint, method); // 再校验 Method 上的注解 @@ -95,7 +92,7 @@ public class SaCheckAspect { private void checkPermission(AnnotatedElement target, ProceedingJoinPoint joinPoint, Method method) { // 校验 @Priv 注解 - Priv priv = (Priv) SaStrategy.instance.getAnnotation.apply(target, Priv.class); + Priv priv = target.getAnnotation(Priv.class); if (priv != null) { IUserType ut = securityService.getUserType(priv.type()); Assert.notNull(ut, () -> SecurityErrorCode.UNKNOWN_USER_TYPE.exception(priv.type())); @@ -112,39 +109,42 @@ public class SaCheckAspect { } } else { // 校验 @SaCheckLogin 注解 - SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.instance.getAnnotation.apply(target, SaCheckLogin.class); - if (checkLogin != null) { - SaManager.getStpLogic(checkLogin.type(), false).checkByAnnotation(checkLogin); + SaCheckLogin checkLogin = target.getAnnotation(SaCheckLogin.class); + if (Objects.nonNull(checkLogin)) { + SaManager.getStpLogic(checkLogin.type(), false).checkLogin(); } - // 校验 @SaCheckRole 注解 - SaCheckRole checkRole = (SaCheckRole) SaStrategy.instance.getAnnotation.apply(target, SaCheckRole.class); - if (checkRole != null) { - SaManager.getStpLogic(checkRole.type(), false).checkByAnnotation(checkRole); + SaCheckRole checkRole = target.getAnnotation(SaCheckRole.class); + if (Objects.nonNull(checkRole)) { + StpLogic stpLogic = SaManager.getStpLogic(checkRole.type(), false); + if (checkRole.mode() == SaMode.OR) { + stpLogic.checkRoleOr(checkRole.value()); + } else { + stpLogic.checkRoleAnd(checkRole.value()); + } } - // 校验 @SaCheckPermission 注解 - SaCheckPermission checkPermission = (SaCheckPermission) SaStrategy.instance.getAnnotation.apply(target, SaCheckPermission.class); - if (checkPermission != null) { - SaManager.getStpLogic(checkPermission.type(), false).checkByAnnotation(checkPermission); + SaCheckPermission checkPermission = target.getAnnotation(SaCheckPermission.class); + if (Objects.nonNull(checkPermission)) { + StpLogic stpLogic = SaManager.getStpLogic(checkPermission.type(), false); + if (checkPermission.mode() == SaMode.OR) { + stpLogic.checkPermissionOr(checkPermission.value()); + } else { + stpLogic.checkPermissionAnd(checkPermission.value()); + } } } // 校验 @SaCheckSafe 注解 - SaCheckSafe checkSafe = (SaCheckSafe) SaStrategy.instance.getAnnotation.apply(target, SaCheckSafe.class); - if (checkSafe != null) { - SaManager.getStpLogic(checkSafe.type(), false).checkByAnnotation(checkSafe); + SaCheckSafe checkSafe = target.getAnnotation(SaCheckSafe.class); + if (Objects.nonNull(checkSafe)) { + SaManager.getStpLogic(checkSafe.type(), false).checkSafe(checkSafe.value()); } // 校验 @SaCheckDisable 注解 - SaCheckDisable checkDisable = (SaCheckDisable) SaStrategy.instance.getAnnotation.apply(target, SaCheckDisable.class); - if (checkDisable != null) { - SaManager.getStpLogic(checkDisable.type(), false).checkByAnnotation(checkDisable); - } - - // 校验 @SaCheckBasic 注解 - SaCheckBasic checkBasic = (SaCheckBasic) SaStrategy.instance.getAnnotation.apply(target, SaCheckBasic.class); - if (checkBasic != null) { - SaBasicUtil.check(checkBasic.realm(), checkBasic.account()); + SaCheckDisable checkDisable = target.getAnnotation(SaCheckDisable.class); + if (Objects.nonNull(checkDisable)) { + StpLogic stpLogic = SaManager.getStpLogic(checkDisable.type(), false); + stpLogic.checkDisableLevel(stpLogic.getLoginId(), checkDisable.type(), checkDisable.level()); } } diff --git a/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/web/BaseRestController.java b/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/web/BaseRestController.java index 1c058bf5..7f363a9a 100644 --- a/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/web/BaseRestController.java +++ b/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/web/BaseRestController.java @@ -15,9 +15,9 @@ */ package com.chestnut.common.security.web; -import com.alibaba.excel.EasyExcel; -import com.alibaba.excel.ExcelWriter; -import com.alibaba.excel.write.metadata.WriteSheet; +import cn.idev.excel.EasyExcel; +import cn.idev.excel.ExcelWriter; +import cn.idev.excel.write.metadata.WriteSheet; import com.baomidou.mybatisplus.core.metadata.IPage; import com.chestnut.common.domain.R; import com.chestnut.common.i18n.I18nUtils; diff --git a/chestnut-common/chestnut-common-staticize/pom.xml b/chestnut-common/chestnut-common-staticize/pom.xml index 3e78555e..8dda59a6 100644 --- a/chestnut-common/chestnut-common-staticize/pom.xml +++ b/chestnut-common/chestnut-common-staticize/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/FreeMarkerUtils.java b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/FreeMarkerUtils.java index 62628f64..fa8648b4 100644 --- a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/FreeMarkerUtils.java +++ b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/FreeMarkerUtils.java @@ -21,11 +21,10 @@ import com.chestnut.common.utils.NumberUtils; import com.chestnut.common.utils.StringUtils; import freemarker.core.Environment; import freemarker.template.*; +import jakarta.validation.constraints.NotBlank; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.nio.charset.StandardCharsets; +import java.util.*; import java.util.Map.Entry; public class FreeMarkerUtils { @@ -80,6 +79,23 @@ public class FreeMarkerUtils { return conflictVariables; } + public static Map getImmutableMapVariable(Environment env, @NotBlank String name) throws TemplateModelException { + TemplateModel model = env.getVariable(name); + if (model == null) { + return Map.of(); + } + if (model instanceof TemplateHashModelEx m) { + Map map = new HashMap<>(m.size()); + for (TemplateModelIterator iterator = m.keys().iterator(); iterator.hasNext();) { + TemplateModel next = iterator.next(); + String key = next.toString(); + map.put(key, m.get(key).toString()); + } + return Collections.unmodifiableMap(map); + } + throw new TemplateModelException(StringUtils.messageFormat("Get map variable failed: {0} = {1}", name, model.toString())); + } + public static String getStringVariable(Environment env, String name) throws TemplateModelException { return parseString(env.getVariable(name), name); } @@ -292,4 +308,22 @@ public class FreeMarkerUtils { throw new TemplateModelException(StringUtils.messageFormat("Eval date boolean variable failed: {0}", name)); } } + + public static String createBy(String html) { + if (StringUtils.isEmpty(html)) { + return html; + } + String text = StringUtils.rightTrim(html); + if (text.length() < 7) { + return html; + } + if (text.substring(text.length() - 7).equalsIgnoreCase("")) { + StringBuilder sb = new StringBuilder(html); + String poweredBy = new String(Base64.getDecoder().decode("PCEtLSBQb3dlcmVkIGJ5IENoZXN0bnV0Q01TLiBUaW1lOiAlcyAtLT4K"), + StandardCharsets.UTF_8).formatted(DateUtils.getDateTime()); + sb.insert(text.length() - 7, poweredBy); + html = sb.toString(); + } + return html; + } } diff --git a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/StaticizeConstants.java b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/StaticizeConstants.java index 0f04200c..b0e513ae 100644 --- a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/StaticizeConstants.java +++ b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/StaticizeConstants.java @@ -61,4 +61,14 @@ public class StaticizeConstants { * 模板解析时间 */ public static final String TemplateVariable_TimeMillis = "TimeMillis"; + + /** + * 模板变量:请求参数 + */ + public final static String TemplateVariable_Request = "Request"; + + /** + * 模板路径参数:页码 + */ + public final static String TemplateParam_PageIndex = "pi"; } diff --git a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/func/IFunction.java b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/func/IFunction.java index fc4bd3e9..d8bd7dfb 100644 --- a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/func/IFunction.java +++ b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/func/IFunction.java @@ -36,6 +36,13 @@ public interface IFunction { * @return */ String getDesc(); + + /** + * 支持版本 + */ + default String supportVersion() { + return ""; + } /** * 获取函数参数定义列表 diff --git a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/func/impl/MaxFunction.java b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/func/impl/MaxFunction.java index 60f525fc..1b41bfe0 100644 --- a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/func/impl/MaxFunction.java +++ b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/func/impl/MaxFunction.java @@ -50,6 +50,11 @@ public class MaxFunction extends AbstractFunc { return DESC; } + @Override + public String supportVersion() { + return "V1.4.2+"; + } + @Override public Object exec0(Object... args) throws TemplateModelException { if (StringUtils.isEmpty(args)) { diff --git a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/func/impl/MinFunction.java b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/func/impl/MinFunction.java index 2f69f88a..c919efcf 100644 --- a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/func/impl/MinFunction.java +++ b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/func/impl/MinFunction.java @@ -51,6 +51,11 @@ public class MinFunction extends AbstractFunc { return DESC; } + @Override + public String supportVersion() { + return "V1.4.2+"; + } + @Override public Object exec0(Object... args) throws TemplateModelException { if (StringUtils.isEmpty(args)) { diff --git a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/tag/ITag.java b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/tag/ITag.java index 86cded60..db1aee68 100644 --- a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/tag/ITag.java +++ b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/tag/ITag.java @@ -37,6 +37,13 @@ public interface ITag { default String getDescription() { return ""; } + + /** + * 支持版本 + */ + default String supportVersion() { + return ""; + } /** * 标签属性定义 diff --git a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/tag/impl/PageBarTag.java b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/tag/impl/PageBarTag.java index e20b1218..4bda9ecb 100644 --- a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/tag/impl/PageBarTag.java +++ b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/tag/impl/PageBarTag.java @@ -24,6 +24,7 @@ import com.chestnut.common.staticize.tag.AbstractTag; import com.chestnut.common.staticize.tag.TagAttr; import com.chestnut.common.staticize.tag.TagAttrOption; import com.chestnut.common.utils.StringUtils; +import com.chestnut.common.utils.XCollectionUtils; import freemarker.core.Environment; import freemarker.template.TemplateException; import freemarker.template.TemplateModel; @@ -51,6 +52,7 @@ public class PageBarTag extends AbstractTag { public final static String ATTR_OPTIONS_TYPE_SIMPLE = "{FREEMARKER.TAG." + TAG_NAME + ".type.simple}"; public final static String ATTR_OPTIONS_TARGET_BLANK = "{FREEMARKER.TAG." + TAG_NAME + ".target._blank}"; public final static String ATTR_OPTIONS_TARGET_SELF = "{FREEMARKER.TAG." + TAG_NAME + ".target._self}"; + public final static String ATTR_USAGE_PARAMS = "{FREEMARKER.TAG." + TAG_NAME + ".params}"; @Override public String getTagName() { @@ -71,6 +73,7 @@ public class PageBarTag extends AbstractTag { final static String ATTR_TARGET = "target"; final static String ATTR_FIRST = "first"; final static String ATTR_LAST = "last"; + final static String ATTR_PARAMS = "params"; @Override public List getTagAttrs() { @@ -79,8 +82,9 @@ public class PageBarTag extends AbstractTag { PageBarType.toTagAttrOptions(), PageBarType.Simple.name())); tagAttrs.add(new TagAttr(ATTR_TARGET, false, TagAttrDataType.STRING, ATTR_USAGE_TARGET, LinkTarget.toTagAttrOptions(), LinkTarget._self.name())); - tagAttrs.add(new TagAttr(ATTR_FIRST, false, TagAttrDataType.STRING, ATTR_USAGE_FIRST, ATTR_DEFAULT_FIRST)); - tagAttrs.add(new TagAttr(ATTR_LAST, false, TagAttrDataType.STRING, ATTR_USAGE_LAST, ATTR_DEFAULT_LAST)); + tagAttrs.add(new TagAttr(ATTR_FIRST, false, TagAttrDataType.STRING, ATTR_USAGE_FIRST)); + tagAttrs.add(new TagAttr(ATTR_LAST, false, TagAttrDataType.STRING, ATTR_USAGE_LAST)); + tagAttrs.add(new TagAttr(ATTR_PARAMS, false, TagAttrDataType.STRING, ATTR_USAGE_PARAMS)); return tagAttrs; } @@ -91,22 +95,34 @@ public class PageBarTag extends AbstractTag { String target = MapUtils.getString(attrs, ATTR_TARGET, LinkTarget._self.name()); String firstPage = MapUtils.getString(attrs, ATTR_FIRST, I18nUtils.get(ATTR_DEFAULT_FIRST)); String lastPage = MapUtils.getString(attrs, ATTR_LAST, I18nUtils.get(ATTR_DEFAULT_LAST)); + String params = MapUtils.getString(attrs, ATTR_PARAMS); + if (StringUtils.isEmpty(params)) { + Map mapVariable = FreeMarkerUtils.getImmutableMapVariable(env, StaticizeConstants.TemplateVariable_Request); + if (!mapVariable.isEmpty()) { + params = XCollectionUtils.join( + mapVariable.entrySet(), + "&", + entry -> entry.getKey().toString() + "=" + entry.getValue().toString(), + entry -> !StaticizeConstants.TemplateParam_PageIndex.equals(entry.getKey()) + ); + } + } env.getOut().write(switch (PageBarType.valueOf(type)) { - case Mini -> generateMinPageBar(target, firstPage, lastPage, env); - case Simple -> generateSimplePageBar(target, firstPage, lastPage, env); + case Mini -> generateMinPageBar(target, firstPage, lastPage, params, env); + case Simple -> generateSimplePageBar(target, firstPage, lastPage, params, env); }); return null; } - private String generateMinPageBar(String target, String firstPage, String lastPage, Environment env) + private String generateMinPageBar(String target, String firstPage, String lastPage, String params, Environment env) throws TemplateException { - return generatePageBar(target, firstPage, lastPage, false, env); + return generatePageBar(target, firstPage, lastPage, params, false, env); } - private String generateSimplePageBar(String target, String firstPage, String lastPage, Environment env) + private String generateSimplePageBar(String target, String firstPage, String lastPage, String params, Environment env) throws TemplateException { - return generatePageBar(target, firstPage, lastPage, true, env); + return generatePageBar(target, firstPage, lastPage, params, true, env); } /** @@ -114,7 +130,7 @@ public class PageBarTag extends AbstractTag { * [首页]...[2][3][4] 5 [6][7][8]...[末页] * 最多显示7个页码,当前页前后各三个 */ - private String generatePageBar(String target, String firstPage, String lastPage, boolean withFirstAndLast, Environment env) + private String generatePageBar(String target, String firstPage, String lastPage, String params, boolean withFirstAndLast, Environment env) throws TemplateException { TemplateContext context = FreeMarkerUtils.getTemplateContext(env); int pageSize = context.getPageSize() == 0 ? 20 : context.getPageSize(); @@ -129,6 +145,10 @@ public class PageBarTag extends AbstractTag { String firstPageLink = FreeMarkerUtils.evalStringVariable(env, StaticizeConstants.TemplateVariable_FirstPage); String otherPageLink = FreeMarkerUtils.evalStringVariable(env, StaticizeConstants.TemplateVariable_OtherPage); + if (StringUtils.isNotEmpty(params)) { + firstPageLink += (firstPageLink.contains("?") ? "&" : "?") + params; + otherPageLink += (otherPageLink.contains("?") ? "&" : "?") + params; + } String temp = "{3}"; StringBuilder sb = new StringBuilder(); sb.append("
"); diff --git a/chestnut-common/chestnut-common-staticize/src/main/resources/i18n/messages.properties b/chestnut-common/chestnut-common-staticize/src/main/resources/i18n/messages.properties index 66642e44..dc966096 100644 --- a/chestnut-common/chestnut-common-staticize/src/main/resources/i18n/messages.properties +++ b/chestnut-common/chestnut-common-staticize/src/main/resources/i18n/messages.properties @@ -1,5 +1,5 @@ FREEMARKER.TAG_ATTR.PAGE=是否分页获取数据 -FREEMARKER.TAG_ATTR.PAGE_SIZE=分页数据条数,默认:20 +FREEMARKER.TAG_ATTR.PAGE_SIZE=(分页)数据条数,默认:10 FREEMARKER.TAG_ATTR.CONDITION=扩展sql条件语句,例如:title like 'a%' FREEMARKER.TAG_ATTR_OPTIONS.BOOLEAN.TRUE=是 FREEMARKER.TAG_ATTR_OPTIONS.BOOLEAN.FALSE=否 @@ -16,6 +16,7 @@ FREEMARKER.TAG.page_bar.first=首页链接名称 FREEMARKER.TAG.page_bar.first.defaultValue=首页 FREEMARKER.TAG.page_bar.last=末页链接名称 FREEMARKER.TAG.page_bar.last.defaultValue=末页 +FREEMARKER.TAG.page_bar.params=URL参数 FREEMARKER.FUNC.urlParameters.DESC=URL参数解析函数,返回参数列表数组`[{"name":"参数名","value":"参数值"},...]` FREEMARKER.FUNC.min.DESC=获取多个数字中最小的数字 @@ -25,7 +26,7 @@ FREEMARKER.FUNC.max.DESC=获取多个数字中最大的数字 FREEMARKER.FUNC.max.Arg1.Name=多个数字参数或数组 FREEMARKER.FUNC.max.Arg1.Desc=获取多个数字中的最大值,如果传入数组会逐个提取数组中的数字进行对比。 FREEMARKER.FUNC.htmlUnescape.DESC=HTML特殊字符反转义函数 -FREEMARKER.FUNC.htmlUnescape.Arg1.Name=待處理字符串 +FREEMARKER.FUNC.htmlUnescape.Arg1.Name=待处理字符串 FREEMARKER.FUNC.randomInt.DESC=获取指定范围随机数,例如:`${randomInt(10,100)}` FREEMARKER.FUNC.randomInt.Arg1.Name=随机数范围最(大/小)值 FREEMARKER.FUNC.randomInt.Arg1.Desc=单个参数时表示[0, arg1),双参数表示[arg1, arg2)。 diff --git a/chestnut-common/chestnut-common-staticize/src/main/resources/i18n/messages_en.properties b/chestnut-common/chestnut-common-staticize/src/main/resources/i18n/messages_en.properties index ccc7e486..dcc5b535 100644 --- a/chestnut-common/chestnut-common-staticize/src/main/resources/i18n/messages_en.properties +++ b/chestnut-common/chestnut-common-staticize/src/main/resources/i18n/messages_en.properties @@ -1,5 +1,5 @@ FREEMARKER.TAG_ATTR.PAGE=Pageable -FREEMARKER.TAG_ATTR.PAGE_SIZE=Page size, default is: 20. +FREEMARKER.TAG_ATTR.PAGE_SIZE=Data(Page) size, default is: 10. FREEMARKER.TAG_ATTR.CONDITION=Extend sql condition, eg: title like 'a%' FREEMARKER.TAG_ATTR_OPTIONS.BOOLEAN.TRUE=Yes FREEMARKER.TAG_ATTR_OPTIONS.BOOLEAN.FALSE=No @@ -16,6 +16,7 @@ FREEMARKER.TAG.page_bar.first=First page link name FREEMARKER.TAG.page_bar.first.defaultValue=First FREEMARKER.TAG.page_bar.last=Last page link name FREEMARKER.TAG.page_bar.last.defaultValue=Last +FREEMARKER.TAG.page_bar.params=URL parameters FREEMARKER.FUNC.urlParameters.DESC=URL parameter parse function, return parameter list like `[{"name":"参数名","value":"参数值"},...]`. FREEMARKER.FUNC.min.DESC=Get the smallest number among multiple numbers. diff --git a/chestnut-common/chestnut-common-staticize/src/main/resources/i18n/messages_zh_TW.properties b/chestnut-common/chestnut-common-staticize/src/main/resources/i18n/messages_zh_TW.properties index 9db47032..6575ffa8 100644 --- a/chestnut-common/chestnut-common-staticize/src/main/resources/i18n/messages_zh_TW.properties +++ b/chestnut-common/chestnut-common-staticize/src/main/resources/i18n/messages_zh_TW.properties @@ -1,5 +1,5 @@ FREEMARKER.TAG_ATTR.PAGE=是否分頁獲取數據 -FREEMARKER.TAG_ATTR.PAGE_SIZE=分頁數據條數,預設:20 +FREEMARKER.TAG_ATTR.PAGE_SIZE=(分頁)數據條數,預設:10 FREEMARKER.TAG_ATTR.CONDITION=擴展sql條件語句,例如:title like 'a%' FREEMARKER.TAG_ATTR_OPTIONS.BOOLEAN.TRUE=是 FREEMARKER.TAG_ATTR_OPTIONS.BOOLEAN.FALSE=否 @@ -16,6 +16,7 @@ FREEMARKER.TAG.page_bar.first=首頁鏈接名稱 FREEMARKER.TAG.page_bar.first.defaultValue=首頁 FREEMARKER.TAG.page_bar.last=末頁鏈接名稱 FREEMARKER.TAG.page_bar.last.defaultValue=末頁 +FREEMARKER.TAG.page_bar.params=URL參數 FREEMARKER.FUNC.urlParameters.DESC=URL參數解析函數,返回參數列表數組`[{"name":"參數名","value":"參數值"},...]` FREEMARKER.FUNC.min.DESC=獲取多個數字中最小的數字 diff --git a/chestnut-common/chestnut-common-storage/pom.xml b/chestnut-common/chestnut-common-storage/pom.xml index 033b612d..a427a9bc 100644 --- a/chestnut-common/chestnut-common-storage/pom.xml +++ b/chestnut-common/chestnut-common-storage/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.5 + 1.5.6 4.0.0 @@ -30,6 +30,20 @@ com.qcloud cos_api + + + org.bouncycastle + bcprov-jdk15on + + + + + + + software.amazon.awssdk + s3 + 2.31.40 + compile diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/FileStorageService.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/FileStorageService.java new file mode 100644 index 00000000..244629e5 --- /dev/null +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/FileStorageService.java @@ -0,0 +1,48 @@ +/* + * 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.common.storage; + +import com.chestnut.common.storage.exception.StorageErrorCode; +import com.chestnut.common.utils.Assert; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.Optional; + +/** + * FileStorageService + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Service +@RequiredArgsConstructor +public class FileStorageService { + + private final Map fileStorageTypes; + + 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; + } + + public Optional optFileStorageType(String type) { + IFileStorageType fileStorageType = fileStorageTypes.get(IFileStorageType.BEAN_NAME_PREIFX + type); + return Optional.ofNullable(fileStorageType); + } +} diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/IFileStorageType.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/IFileStorageType.java index e9efe423..d9a8c4ba 100644 --- a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/IFileStorageType.java +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/IFileStorageType.java @@ -35,17 +35,19 @@ public interface IFileStorageType { /** * 测试连接 */ - default boolean testConnection(String endpoint, String accessKey, String accessSecret) { + default boolean testConnection(String endpoint, String region, String accessKey, String accessSecret) { return true; } /** * 重置连接客户端 * - * @param clientKey - * @param args + * @param endpoint endpoint + * @param region region + * @param accessKey accessKey + * @param accessSecret accessSecret */ - default void reloadClient(String endpoint, String accessKey, String accessSecret) { + default void reloadClient(String endpoint, String region, String accessKey, String accessSecret) { } diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/OSSClient.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/OSSClient.java index 43839f05..05cb7816 100644 --- a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/OSSClient.java +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/OSSClient.java @@ -15,25 +15,14 @@ */ package com.chestnut.common.storage; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter public class OSSClient { private T client; private long lastActiveTime; - - public T getClient() { - return client; - } - - public void setClient(T client) { - this.client = client; - } - - public long getLastActiveTime() { - return lastActiveTime; - } - - public void setLastActiveTime(long lastActiveTime) { - this.lastActiveTime = lastActiveTime; - } } \ No newline at end of file diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageBasicArgs.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageBasicArgs.java new file mode 100644 index 00000000..d9d15a3f --- /dev/null +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageBasicArgs.java @@ -0,0 +1,50 @@ +/* + * 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.common.storage; + +import lombok.Getter; +import lombok.Setter; + +/** + * BasicStorageArgs + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Getter +@Setter +public class StorageBasicArgs { + + /** + * 访问API接口地址 + */ + private String endpoint; + + /** + * 区域 + */ + private String region; + + /** + * 用户名 + */ + private String accessKey; + + /** + * 访问密码 + */ + private String accessSecret; +} diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageCopyArgs.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageCopyArgs.java index b4e14ef9..ef47539e 100644 --- a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageCopyArgs.java +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageCopyArgs.java @@ -15,29 +15,12 @@ */ package com.chestnut.common.storage; -import lombok.Builder; import lombok.Getter; import lombok.Setter; @Getter @Setter -@Builder -public class StorageCopyArgs { - - /** - * 访问API接口地址 - */ - private String endpoint; - - /** - * 用户名 - */ - private String accessKey; - - /** - * 访问密码 - */ - private String accessSecret; +public class StorageCopyArgs extends StorageBasicArgs { /** * 存储空间名 @@ -53,4 +36,60 @@ public class StorageCopyArgs { * 目标路径 */ private String destPath; + + public static class Builder { + + private String endpoint; + private String region; + private String accessKey; + private String accessSecret; + private String bucket; + private String sourcePath; + private String destPath; + + public StorageCopyArgs build() { + StorageCopyArgs storageCopyArgs = new StorageCopyArgs(); + storageCopyArgs.setEndpoint(endpoint); + storageCopyArgs.setRegion(region); + storageCopyArgs.setAccessKey(accessKey); + storageCopyArgs.setAccessSecret(accessSecret); + storageCopyArgs.setBucket(bucket); + storageCopyArgs.setSourcePath(sourcePath); + storageCopyArgs.setDestPath(destPath); + return storageCopyArgs; + } + + public StorageCopyArgs.Builder endpoint(String endpoint) { + this.endpoint = endpoint; + return this; + } + public StorageCopyArgs.Builder region(String region) { + this.region = region; + return this; + } + public StorageCopyArgs.Builder accessKey(String accessKey) { + this.accessKey = accessKey; + return this; + } + public StorageCopyArgs.Builder accessSecret(String accessSecret) { + this.accessSecret = accessSecret; + return this; + } + public StorageCopyArgs.Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + public StorageCopyArgs.Builder sourcePath(String sourcePath) { + this.sourcePath = sourcePath; + return this; + } + public StorageCopyArgs.Builder destPath(String destPath) { + this.destPath = destPath; + return this; + } + } + + public static Builder builder() { + return new Builder(); + } } diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageCreateBucketArgs.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageCreateBucketArgs.java index 05b8924a..7eb52d7a 100644 --- a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageCreateBucketArgs.java +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageCreateBucketArgs.java @@ -15,32 +15,59 @@ */ package com.chestnut.common.storage; -import lombok.Builder; import lombok.Getter; import lombok.Setter; @Getter @Setter -@Builder -public class StorageCreateBucketArgs { - - /** - * 访问API接口地址 - */ - private String endpoint; - - /** - * 用户名 - */ - private String accessKey; - - /** - * 访问密码 - */ - private String accessSecret; +public class StorageCreateBucketArgs extends StorageBasicArgs { /** * 存储空间名 */ private String bucket; + + public static class Builder { + + private String endpoint; + private String region; + private String accessKey; + private String accessSecret; + private String bucket; + + public StorageCreateBucketArgs build() { + StorageCreateBucketArgs storageCreateBucketArgs = new StorageCreateBucketArgs(); + storageCreateBucketArgs.setEndpoint(endpoint); + storageCreateBucketArgs.setRegion(region); + storageCreateBucketArgs.setAccessKey(accessKey); + storageCreateBucketArgs.setAccessSecret(accessSecret); + storageCreateBucketArgs.setBucket(bucket); + return storageCreateBucketArgs; + } + + public Builder endpoint(String endpoint) { + this.endpoint = endpoint; + return this; + } + public Builder region(String region) { + this.region = region; + return this; + } + public Builder accessKey(String accessKey) { + this.accessKey = accessKey; + return this; + } + public Builder accessSecret(String accessSecret) { + this.accessSecret = accessSecret; + return this; + } + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + } + + public static Builder builder() { + return new Builder(); + } } diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageExistArgs.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageExistArgs.java index 25302971..9478e753 100644 --- a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageExistArgs.java +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageExistArgs.java @@ -15,29 +15,12 @@ */ package com.chestnut.common.storage; -import lombok.Builder; import lombok.Getter; import lombok.Setter; @Getter @Setter -@Builder -public class StorageExistArgs { - - /** - * 访问API接口地址 - */ - private String endpoint; - - /** - * 用户名 - */ - private String accessKey; - - /** - * 访问密码 - */ - private String accessSecret; +public class StorageExistArgs extends StorageBasicArgs { /** * 存储空间名 @@ -48,4 +31,54 @@ public class StorageExistArgs { * 路径 */ private String path; + + public static class Builder { + + private String endpoint; + private String region; + private String accessKey; + private String accessSecret; + private String bucket; + private String path; + + public StorageExistArgs build() { + StorageExistArgs storageExistArgs = new StorageExistArgs(); + storageExistArgs.setEndpoint(endpoint); + storageExistArgs.setRegion(region); + storageExistArgs.setAccessKey(accessKey); + storageExistArgs.setAccessSecret(accessSecret); + storageExistArgs.setBucket(bucket); + storageExistArgs.setPath(path); + return storageExistArgs; + } + + public Builder endpoint(String endpoint) { + this.endpoint = endpoint; + return this; + } + public Builder region(String region) { + this.region = region; + return this; + } + public Builder accessKey(String accessKey) { + this.accessKey = accessKey; + return this; + } + public Builder accessSecret(String accessSecret) { + this.accessSecret = accessSecret; + return this; + } + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + public Builder path(String path) { + this.path = path; + return this; + } + } + + public static Builder builder() { + return new Builder(); + } } diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageListArgs.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageListArgs.java index 43ed5d81..fb45b8f4 100644 --- a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageListArgs.java +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageListArgs.java @@ -15,29 +15,12 @@ */ package com.chestnut.common.storage; -import lombok.Builder; import lombok.Getter; import lombok.Setter; @Getter @Setter -@Builder -public class StorageListArgs { - - /** - * 访问API接口地址 - */ - private String endpoint; - - /** - * 用户名 - */ - private String accessKey; - - /** - * 访问密码 - */ - private String accessSecret; +public class StorageListArgs extends StorageBasicArgs { /** * 存储空间名 @@ -58,4 +41,66 @@ public class StorageListArgs { * 列举文件的最大个数 */ private int maxKeys; + + public static class Builder { + + private String endpoint; + private String region; + private String accessKey; + private String accessSecret; + private String bucket; + private String continuationToken; + private String prefix; + private int maxKeys = 1000; + + public StorageListArgs build() { + StorageListArgs args = new StorageListArgs(); + args.setEndpoint(endpoint); + args.setRegion(region); + args.setAccessKey(accessKey); + args.setAccessSecret(accessSecret); + args.setBucket(bucket); + args.setContinuationToken(continuationToken); + args.setPrefix(prefix); + args.setMaxKeys(maxKeys); + return args; + } + + public Builder endpoint(String endpoint) { + this.endpoint = endpoint; + return this; + } + public Builder region(String region) { + this.region = region; + return this; + } + public Builder accessKey(String accessKey) { + this.accessKey = accessKey; + return this; + } + public Builder accessSecret(String accessSecret) { + this.accessSecret = accessSecret; + return this; + } + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + public Builder continuationToken(String continuationToken) { + this.continuationToken = continuationToken; + return this; + } + public Builder prefix(String prefix) { + this.prefix = prefix; + return this; + } + public Builder maxKeys(int maxKeys) { + this.maxKeys = maxKeys; + return this; + } + } + + public static Builder builder() { + return new Builder(); + } } diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageMoveArgs.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageMoveArgs.java index 05c67e83..80156940 100644 --- a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageMoveArgs.java +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageMoveArgs.java @@ -15,29 +15,12 @@ */ package com.chestnut.common.storage; -import lombok.Builder; import lombok.Getter; import lombok.Setter; @Getter @Setter -@Builder -public class StorageMoveArgs { - - /** - * 访问API接口地址 - */ - private String endpoint; - - /** - * 用户名 - */ - private String accessKey; - - /** - * 访问密码 - */ - private String accessSecret; +public class StorageMoveArgs extends StorageBasicArgs { /** * 存储空间名 @@ -53,4 +36,60 @@ public class StorageMoveArgs { * 目标路径 */ private String destPath; + + public static class Builder { + + private String endpoint; + private String region; + private String accessKey; + private String accessSecret; + private String bucket; + private String sourcePath; + private String destPath; + + public StorageMoveArgs build() { + StorageMoveArgs args = new StorageMoveArgs(); + args.setEndpoint(endpoint); + args.setRegion(region); + args.setAccessKey(accessKey); + args.setAccessSecret(accessSecret); + args.setBucket(bucket); + args.setSourcePath(sourcePath); + args.setDestPath(destPath); + return args; + } + + public Builder endpoint(String endpoint) { + this.endpoint = endpoint; + return this; + } + public Builder region(String region) { + this.region = region; + return this; + } + public Builder accessKey(String accessKey) { + this.accessKey = accessKey; + return this; + } + public Builder accessSecret(String accessSecret) { + this.accessSecret = accessSecret; + return this; + } + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + public Builder sourcePath(String sourcePath) { + this.sourcePath = sourcePath; + return this; + } + public Builder destPath(String destPath) { + this.destPath = destPath; + return this; + } + } + + public static Builder builder() { + return new Builder(); + } } diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageReadArgs.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageReadArgs.java index f26c5746..3b5c7399 100644 --- a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageReadArgs.java +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageReadArgs.java @@ -15,29 +15,12 @@ */ package com.chestnut.common.storage; -import lombok.Builder; import lombok.Getter; import lombok.Setter; @Getter @Setter -@Builder -public class StorageReadArgs { - - /** - * 访问API接口地址 - */ - private String endpoint; - - /** - * 用户名 - */ - private String accessKey; - - /** - * 访问密码 - */ - private String accessSecret; +public class StorageReadArgs extends StorageBasicArgs { /** * 存储空间名 @@ -48,4 +31,54 @@ public class StorageReadArgs { * 路径 */ private String path; + + public static class Builder { + + private String endpoint; + private String region; + private String accessKey; + private String accessSecret; + private String bucket; + private String path; + + public StorageReadArgs build() { + StorageReadArgs args = new StorageReadArgs(); + args.setEndpoint(endpoint); + args.setRegion(region); + args.setAccessKey(accessKey); + args.setAccessSecret(accessSecret); + args.setBucket(bucket); + args.setPath(path); + return args; + } + + public Builder endpoint(String endpoint) { + this.endpoint = endpoint; + return this; + } + public Builder region(String region) { + this.region = region; + return this; + } + public Builder accessKey(String accessKey) { + this.accessKey = accessKey; + return this; + } + public Builder accessSecret(String accessSecret) { + this.accessSecret = accessSecret; + return this; + } + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + public Builder path(String path) { + this.path = path; + return this; + } + } + + public static Builder builder() { + return new Builder(); + } } diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageRemoveArgs.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageRemoveArgs.java index 59b01cc6..c4c3ccb7 100644 --- a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageRemoveArgs.java +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageRemoveArgs.java @@ -15,29 +15,12 @@ */ package com.chestnut.common.storage; -import lombok.Builder; import lombok.Getter; import lombok.Setter; @Getter @Setter -@Builder -public class StorageRemoveArgs { - - /** - * 访问API接口地址 - */ - private String endpoint; - - /** - * 用户名 - */ - private String accessKey; - - /** - * 访问密码 - */ - private String accessSecret; +public class StorageRemoveArgs extends StorageBasicArgs { /** * 存储空间名 @@ -48,4 +31,54 @@ public class StorageRemoveArgs { * 路径 */ private String path; + + public static class Builder { + + private String endpoint; + private String region; + private String accessKey; + private String accessSecret; + private String bucket; + private String path; + + public StorageRemoveArgs build() { + StorageRemoveArgs args = new StorageRemoveArgs(); + args.setEndpoint(endpoint); + args.setRegion(region); + args.setAccessKey(accessKey); + args.setAccessSecret(accessSecret); + args.setBucket(bucket); + args.setPath(path); + return args; + } + + public Builder endpoint(String endpoint) { + this.endpoint = endpoint; + return this; + } + public Builder region(String region) { + this.region = region; + return this; + } + public Builder accessKey(String accessKey) { + this.accessKey = accessKey; + return this; + } + public Builder accessSecret(String accessSecret) { + this.accessSecret = accessSecret; + return this; + } + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + public Builder path(String path) { + this.path = path; + return this; + } + } + + public static Builder builder() { + return new Builder(); + } } diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageWriteArgs.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageWriteArgs.java index 2847adc6..61df8372 100644 --- a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageWriteArgs.java +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/StorageWriteArgs.java @@ -15,31 +15,14 @@ */ package com.chestnut.common.storage; -import java.io.InputStream; - -import lombok.Builder; import lombok.Getter; import lombok.Setter; +import java.io.InputStream; + @Getter @Setter -@Builder -public class StorageWriteArgs { - - /** - * 访问API接口地址 - */ - private String endpoint; - - /** - * 用户名 - */ - private String accessKey; - - /** - * 访问密码 - */ - private String accessSecret; +public class StorageWriteArgs extends StorageBasicArgs { /** * 存储空间名 @@ -59,5 +42,67 @@ public class StorageWriteArgs { /** * 文件大小 */ - private Long length; + private long length; + + public static class Builder { + + private String endpoint; + private String region; + private String accessKey; + private String accessSecret; + private String bucket; + private String path; + private InputStream inputStream; + private long length; + + public StorageWriteArgs build() { + StorageWriteArgs args = new StorageWriteArgs(); + args.setEndpoint(endpoint); + args.setRegion(region); + args.setAccessKey(accessKey); + args.setAccessSecret(accessSecret); + args.setBucket(bucket); + args.setPath(path); + args.setInputStream(inputStream); + args.setLength(length); + return args; + } + + public Builder endpoint(String endpoint) { + this.endpoint = endpoint; + return this; + } + public Builder region(String region) { + this.region = region; + return this; + } + public Builder accessKey(String accessKey) { + this.accessKey = accessKey; + return this; + } + public Builder accessSecret(String accessSecret) { + this.accessSecret = accessSecret; + return this; + } + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + public Builder path(String path) { + this.path = path; + return this; + } + public Builder inputStream(InputStream inputStream) { + this.inputStream = inputStream; + return this; + } + public Builder length(long length) { + this.length = length; + return this; + } + } + + public static Builder builder() { + return new Builder(); + } } diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/cos/TencentStorageType.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/cos/TencentStorageType.java index 941704a7..90d21ec4 100644 --- a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/cos/TencentStorageType.java +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/cos/TencentStorageType.java @@ -50,8 +50,8 @@ public class TencentStorageType implements IFileStorageType { } @Override - public boolean testConnection(String endpoint, String accessKey, String accessSecret) { - OSSClient client = this.getClient(endpoint, accessKey, accessSecret); + public boolean testConnection(String endpoint, String region, String accessKey, String accessSecret) { + OSSClient client = this.getClient(endpoint, region, accessKey, accessSecret); try { client.getClient().listBuckets(); return true; @@ -61,13 +61,13 @@ public class TencentStorageType implements IFileStorageType { } @Override - public void reloadClient(String endpoint, String accessKey, String accessSecret) { + public void reloadClient(String endpoint, String region, String accessKey, String accessSecret) { OSSClient client = this.clients.get(endpoint); if (client != null) { client.getClient().shutdown(); this.clients.remove(endpoint); } - this.getClient(endpoint, accessKey, accessSecret); + this.getClient(endpoint, region, accessKey, accessSecret); } /** @@ -138,14 +138,20 @@ public class TencentStorageType implements IFileStorageType { // 复制后删除源 client.getClient().deleteObject(args.getBucket(), args.getSourcePath()); } - + private OSSClient getClient(String endpoint, String accessKey, String accessSecret) { + return getClient(endpoint, null, accessKey, accessSecret); + } + + private OSSClient getClient(String endpoint, String region, String accessKey, String accessSecret) { OSSClient client = this.clients.get(endpoint); if (client == null) { client = new OSSClient<>(); COSCredentials credentials = new BasicCOSCredentials(accessKey, accessSecret); - String region = StringUtils.substringAfterLast( - StringUtils.substringBefore(endpoint, ".myqcloud.com"), "."); + if (StringUtils.isEmpty(region)) { + region = StringUtils.substringAfterLast( + StringUtils.substringBefore(endpoint, ".myqcloud.com"), "."); + } client.setClient(new COSClient(credentials, new ClientConfig(new Region(region)))); this.clients.put(endpoint, client); } diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/minio/MinIOFileStorageType.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/minio/MinIOFileStorageType.java index 08dc31d4..55a9c4d2 100644 --- a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/minio/MinIOFileStorageType.java +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/minio/MinIOFileStorageType.java @@ -36,7 +36,7 @@ public class MinIOFileStorageType implements IFileStorageType { public final static String TYPE = "MinIO"; - private Map> clients = new HashMap<>(); + private final Map> clients = new HashMap<>(); @Override public String getType() { @@ -48,11 +48,11 @@ public class MinIOFileStorageType implements IFileStorageType { return I18nUtils.get("{STORAGE.TYPE." + TYPE + "}"); } - private OSSClient getClient(String endpoint, String accessKey, String accessSecret) { + private OSSClient getClient(String endpoint, String region, String accessKey, String accessSecret) { OSSClient client = this.clients.get(endpoint); if (client == null) { client = new OSSClient<>(); - client.setClient(MinioClient.builder().endpoint(endpoint).credentials(accessKey, accessSecret).build()); + client.setClient(MinioClient.builder().endpoint(endpoint).region(region).credentials(accessKey, accessSecret).build()); this.clients.put(endpoint, client); } client.setLastActiveTime(System.currentTimeMillis()); @@ -60,8 +60,8 @@ public class MinIOFileStorageType implements IFileStorageType { } @Override - public boolean testConnection(String endpoint, String accessKey, String accessSecret) { - OSSClient client = this.getClient(endpoint, accessKey, accessSecret); + public boolean testConnection(String endpoint, String region, String accessKey, String accessSecret) { + OSSClient client = this.getClient(endpoint, region, accessKey, accessSecret); try { client.getClient().listBuckets(); return true; @@ -78,7 +78,7 @@ public class MinIOFileStorageType implements IFileStorageType { if (StringUtils.isEmpty(args.getBucket())) { throw CommonErrorCode.NOT_EMPTY.exception("bucket"); } - OSSClient client = this.getClient(args.getEndpoint(), args.getAccessKey(), args.getAccessSecret()); + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); this.createBucket0(client, args.getBucket()); } @@ -96,15 +96,15 @@ public class MinIOFileStorageType implements IFileStorageType { } @Override - public void reloadClient(String endpoint, String accessKey, String accessSecret) { + public void reloadClient(String endpoint, String region, String accessKey, String accessSecret) { this.clients.remove(endpoint); - this.getClient(endpoint, accessKey, accessSecret); + this.getClient(endpoint, region, accessKey, accessSecret); } @Override public InputStream read(StorageReadArgs args) { try { - OSSClient client = this.getClient(args.getEndpoint(), args.getAccessKey(), + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(args.getBucket()).object(args.getPath()) .build(); @@ -116,7 +116,7 @@ public class MinIOFileStorageType implements IFileStorageType { @Override public List list(StorageListArgs args) { - OSSClient client = this.getClient(args.getEndpoint(), args.getAccessKey(), + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); ListObjectsArgs listObjectsArgs = new ListObjectsArgs.Builder() @@ -144,7 +144,7 @@ public class MinIOFileStorageType implements IFileStorageType { throw CommonErrorCode.NOT_EMPTY.exception("inputStream"); } String mimetype = Mimetypes.getInstance().getMimetype(args.getPath()); - OSSClient client = this.getClient(args.getEndpoint(), args.getAccessKey(), + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(args.getBucket()).object(args.getPath()) .contentType(mimetype).stream(args.getInputStream(), args.getInputStream().available(), -1).build(); @@ -157,7 +157,7 @@ public class MinIOFileStorageType implements IFileStorageType { @Override public void remove(StorageRemoveArgs args) { try { - OSSClient client = this.getClient(args.getEndpoint(), args.getAccessKey(), + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(args.getBucket()) .object(args.getPath()).build(); @@ -169,7 +169,7 @@ public class MinIOFileStorageType implements IFileStorageType { @Override public void copy(StorageCopyArgs args) { - OSSClient client = this.getClient(args.getEndpoint(), args.getAccessKey(), args.getAccessSecret()); + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); ComposeSource composeSource = new ComposeSource( CopySource.builder().bucket(args.getBucket()).object(args.getSourcePath()).build()); ComposeObjectArgs objectArgs = ComposeObjectArgs.builder().bucket(args.getBucket()) @@ -184,7 +184,7 @@ public class MinIOFileStorageType implements IFileStorageType { @Override public void move(StorageMoveArgs args) { - OSSClient client = this.getClient(args.getEndpoint(), args.getAccessKey(), args.getAccessSecret()); + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); ComposeSource composeSource = new ComposeSource( CopySource.builder().bucket(args.getBucket()).object(args.getSourcePath()).build()); ComposeObjectArgs objectArgs = ComposeObjectArgs.builder().bucket(args.getBucket()) diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/oss/AliyunFileStorageType.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/oss/AliyunFileStorageType.java index 0e2b7b70..1b2da838 100644 --- a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/oss/AliyunFileStorageType.java +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/oss/AliyunFileStorageType.java @@ -46,7 +46,7 @@ public class AliyunFileStorageType implements IFileStorageType { } @Override - public boolean testConnection(String endPoint, String accessKey, String accessSecret) { + public boolean testConnection(String endPoint, String region, String accessKey, String accessSecret) { OSSClient client = this.getClient(endPoint, accessKey, accessSecret); try { client.getClient().getUserQosInfo(); @@ -57,7 +57,7 @@ public class AliyunFileStorageType implements IFileStorageType { } @Override - public void reloadClient(String endpoint, String accessKey, String accessSecret) { + public void reloadClient(String endpoint, String region, String accessKey, String accessSecret) { OSSClient client = this.clients.get(endpoint); if (client != null) { client.getClient().shutdown(); diff --git a/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/s3/AmazonS3FileStorageType.java b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/s3/AmazonS3FileStorageType.java new file mode 100644 index 00000000..c1efa5a9 --- /dev/null +++ b/chestnut-common/chestnut-common-storage/src/main/java/com/chestnut/common/storage/s3/AmazonS3FileStorageType.java @@ -0,0 +1,263 @@ +/* + * 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.common.storage.s3; + +import com.chestnut.common.exception.CommonErrorCode; +import com.chestnut.common.i18n.I18nUtils; +import com.chestnut.common.storage.*; +import com.chestnut.common.utils.Assert; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.core.sync.ResponseTransformer; +import software.amazon.awssdk.core.waiters.WaiterResponse; +import software.amazon.awssdk.http.HttpStatusCode; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.model.*; +import software.amazon.awssdk.services.s3.paginators.ListObjectsV2Iterable; +import software.amazon.awssdk.services.s3.waiters.S3Waiter; + +import java.io.InputStream; +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@Component(IFileStorageType.BEAN_NAME_PREIFX + AmazonS3FileStorageType.TYPE) +public class AmazonS3FileStorageType implements IFileStorageType { + + public final static String TYPE = "AmazonS3"; + + private final Map> clients = new HashMap<>(); + + @Override + public String getType() { + return TYPE; + } + + @Override + public String getName() { + return I18nUtils.get("{STORAGE.TYPE." + TYPE + "}"); + } + + @Override + public boolean testConnection(String endPoint, String region, String accessKey, String accessSecret) { + OSSClient client = this.getClient(endPoint, region, accessKey, accessSecret); + try { + client.getClient(); + return true; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void reloadClient(String endpoint, String region, String accessKey, String accessSecret) { + OSSClient client = this.clients.get(endpoint); + if (client != null) { + client.getClient().close(); + this.clients.remove(endpoint); + } + this.getClient(endpoint, region, accessKey, accessSecret); + log.info("The amazon s3 client reloaded: " + endpoint); + } + + /** + * 创建存储桶 + */ + @Override + public void createBucket(StorageCreateBucketArgs args) { + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); + try(S3Waiter s3Waiter = client.getClient().waiter()) { + CreateBucketRequest bucketRequest = CreateBucketRequest.builder() + .bucket(args.getBucket()) + .build(); + + client.getClient().createBucket(bucketRequest); + HeadBucketRequest bucketRequestWait = HeadBucketRequest.builder() + .bucket(args.getBucket()) + .build(); + + // Wait until the bucket is created and print out the response. + WaiterResponse waiterResponse = s3Waiter.waitUntilBucketExists(bucketRequestWait); + Optional response = waiterResponse.matched().response(); + response.ifPresent(res -> { + log.info("AmazonS3: bucket [{}] is ready.", res.bucketLocationName()); + }); + } catch (S3Exception e) { + log.error(e.awsErrorDetails().errorMessage(), e); + } + } + + @Override + public boolean exists(StorageExistArgs args) { + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); + try { + Assert.notEmpty(args.getBucket(), () -> CommonErrorCode.NOT_EMPTY.exception("bucketName")); + client.getClient().getBucketAcl(r -> r.bucket(args.getBucket())); + return true; + } catch (AwsServiceException ase) { + // A redirect error or an AccessDenied exception means the bucket exists but it's not in this region + // or we don't have permissions to it. + if ((ase.statusCode() == HttpStatusCode.MOVED_PERMANENTLY) || "AccessDenied".equals(ase.awsErrorDetails().errorCode())) { + return true; + } + if (ase.statusCode() == HttpStatusCode.NOT_FOUND) { + return false; + } + throw ase; + } + } + + @Override + public InputStream read(StorageReadArgs args) { + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); + try { + GetObjectRequest objectRequest = GetObjectRequest + .builder() + .key(args.getPath()) + .bucket(args.getBucket()) + .build(); + + return client.getClient().getObject(objectRequest, ResponseTransformer.toInputStream()); + } catch (S3Exception e) { + log.error(e.awsErrorDetails().errorMessage(), e); + throw e; + } + } + + @Override + public List list(StorageListArgs args) { + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); + try { + ListObjectsV2Request listReq = ListObjectsV2Request.builder() + .bucket(args.getBucket()) + .maxKeys(args.getMaxKeys()) + .prefix(args.getPrefix()) + .build(); + + ListObjectsV2Iterable listRes = client.getClient().listObjectsV2Paginator(listReq); + return listRes.stream() + .flatMap(r -> r.contents().stream()) + .map(S3Object::key).toList(); + } catch (S3Exception e) { + log.error(e.awsErrorDetails().errorMessage(), e); + throw e; + } + } + + @Override + public void write(StorageWriteArgs args) { + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); + try { + PutObjectRequest putOb = PutObjectRequest.builder() + .bucket(args.getBucket()) + .key(args.getPath()) + .build(); + + client.getClient().putObject(putOb, RequestBody.fromInputStream(args.getInputStream(), args.getLength())); + } catch (S3Exception e) { + log.error(e.awsErrorDetails().errorMessage(), e); + throw e; + } + } + + @Override + public void remove(StorageRemoveArgs args) { + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); + try { + DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder() + .bucket(args.getBucket()) + .key(args.getPath()) + .build(); + + client.getClient().deleteObject(deleteObjectRequest); + } catch (S3Exception e) { + log.error(e.awsErrorDetails().errorMessage(), e); + throw e; + } + } + + @Override + public void copy(StorageCopyArgs args) { + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); + try { + CopyObjectRequest copyObjectRequest = CopyObjectRequest.builder() + .sourceBucket(args.getBucket()) + .sourceKey(args.getSourcePath()) + .destinationBucket(args.getBucket()) + .destinationKey(args.getDestPath()) + .build(); + + client.getClient().copyObject(copyObjectRequest); + } catch (S3Exception e) { + log.error(e.awsErrorDetails().errorMessage(), e); + throw e; + } + } + + @Override + public void move(StorageMoveArgs args) { + OSSClient client = this.getClient(args.getEndpoint(), args.getRegion(), args.getAccessKey(), args.getAccessSecret()); + try { + CopyObjectRequest copyObjectRequest = CopyObjectRequest.builder() + .sourceBucket(args.getBucket()) + .sourceKey(args.getSourcePath()) + .destinationBucket(args.getBucket()) + .destinationKey(args.getDestPath()) + .build(); + client.getClient().copyObject(copyObjectRequest); + // 复制后删除源 + client.getClient().deleteObject(b -> b.bucket(args.getBucket()).key(args.getSourcePath())); + } catch (S3Exception e) { + log.error(e.awsErrorDetails().errorMessage(), e); + throw e; + } + } + + private OSSClient getClient(String endpoint, String region, String accessKey, String accessSecret) { + OSSClient client = this.clients.get(endpoint); + if (client == null) { + client = new OSSClient<>(); + S3Client s3Client = S3Client.builder() + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, accessSecret))) + .region(Region.AWS_GLOBAL) + .endpointOverride(URI.create(endpoint)) + .serviceConfiguration(S3Configuration.builder() + .pathStyleAccessEnabled(false) + .chunkedEncodingEnabled(false) + .build()) + .build(); + client.setClient(s3Client); + this.clients.put(endpoint, client); + } + client.setLastActiveTime(System.currentTimeMillis()); + return client; + } + + @PreDestroy + public void destroy() { + this.clients.values().forEach(client -> client.getClient().close()); + } +} diff --git a/chestnut-common/chestnut-common-storage/src/main/resources/i18n/messages.properties b/chestnut-common/chestnut-common-storage/src/main/resources/i18n/messages.properties index e53b476b..6371ada3 100644 --- a/chestnut-common/chestnut-common-storage/src/main/resources/i18n/messages.properties +++ b/chestnut-common/chestnut-common-storage/src/main/resources/i18n/messages.properties @@ -5,3 +5,4 @@ STORAGE.TYPE.Local=本地存储 STORAGE.TYPE.TencentCOS=腾讯云COS STORAGE.TYPE.AliyunOSS=阿里云OSS STORAGE.TYPE.MinIO=MinIO +STORAGE.TYPE.AmazonS3=AmazonS3 diff --git a/chestnut-common/chestnut-common-storage/src/main/resources/i18n/messages_en.properties b/chestnut-common/chestnut-common-storage/src/main/resources/i18n/messages_en.properties index 7e98deb3..2c8d8d2a 100644 --- a/chestnut-common/chestnut-common-storage/src/main/resources/i18n/messages_en.properties +++ b/chestnut-common/chestnut-common-storage/src/main/resources/i18n/messages_en.properties @@ -4,4 +4,5 @@ ERRCODE.STORAGE.UNSUPPORTED_STORAGE_TYPE=Unsupported storage type: {0} STORAGE.TYPE.Local=Local STORAGE.TYPE.TencentCOS=TencentCOS STORAGE.TYPE.AliyunOSS=AliyunOSS -STORAGE.TYPE.MinIO=MinIO \ No newline at end of file +STORAGE.TYPE.MinIO=MinIO +STORAGE.TYPE.AmazonS3=AmazonS3 \ No newline at end of file diff --git a/chestnut-common/chestnut-common-storage/src/main/resources/i18n/messages_zh_TW.properties b/chestnut-common/chestnut-common-storage/src/main/resources/i18n/messages_zh_TW.properties index 47dcea8b..723b77d6 100644 --- a/chestnut-common/chestnut-common-storage/src/main/resources/i18n/messages_zh_TW.properties +++ b/chestnut-common/chestnut-common-storage/src/main/resources/i18n/messages_zh_TW.properties @@ -5,3 +5,4 @@ STORAGE.TYPE.Local=本地存儲 STORAGE.TYPE.TencentCOS=騰訊雲COS STORAGE.TYPE.AliyunOSS=阿里雲OSS STORAGE.TYPE.MinIO=MinIO +STORAGE.TYPE.AmazonS3=亞馬遜S3 diff --git a/chestnut-common/pom.xml b/chestnut-common/pom.xml index be67aa81..07e61dc3 100644 --- a/chestnut-common/pom.xml +++ b/chestnut-common/pom.xml @@ -5,7 +5,7 @@ com.chestnut chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-modules/chestnut-comment/pom.xml b/chestnut-modules/chestnut-comment/pom.xml index 43ebfff3..70ea6c73 100644 --- a/chestnut-modules/chestnut-comment/pom.xml +++ b/chestnut-modules/chestnut-comment/pom.xml @@ -3,7 +3,7 @@ chestnut-modules com.chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-modules/chestnut-generator/pom.xml b/chestnut-modules/chestnut-generator/pom.xml index 973b10e9..48c6ce3b 100644 --- a/chestnut-modules/chestnut-generator/pom.xml +++ b/chestnut-modules/chestnut-generator/pom.xml @@ -5,7 +5,7 @@ chestnut-modules com.chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-modules/chestnut-member/pom.xml b/chestnut-modules/chestnut-member/pom.xml index d0bb0ff1..a86bfd87 100644 --- a/chestnut-modules/chestnut-member/pom.xml +++ b/chestnut-modules/chestnut-member/pom.xml @@ -3,7 +3,7 @@ chestnut-modules com.chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/config/properties/MemberProperties.java b/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/config/properties/MemberProperties.java index 5765262a..94cbf73c 100644 --- a/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/config/properties/MemberProperties.java +++ b/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/config/properties/MemberProperties.java @@ -19,6 +19,8 @@ import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; +import java.util.List; + /** * 会员配置 * diff --git a/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/service/impl/MemberServiceImpl.java b/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/service/impl/MemberServiceImpl.java index 91659da8..2b249ed5 100644 --- a/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/service/impl/MemberServiceImpl.java +++ b/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/service/impl/MemberServiceImpl.java @@ -34,6 +34,8 @@ import com.chestnut.member.mapper.MemberLevelMapper; import com.chestnut.member.mapper.MemberMapper; import com.chestnut.member.mapper.MemberSignInLogMapper; import com.chestnut.member.service.IMemberService; +import com.chestnut.system.config.properties.SysProperties; +import com.chestnut.system.exception.SysErrorCode; import com.chestnut.system.service.ISecurityConfigService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -157,6 +159,8 @@ public class MemberServiceImpl extends ServiceImpl impleme } } + private final SysProperties sysProperties; + @Override public String uploadAvatarByBase64(Long memberId, String base64Data) throws IOException { if (!ImageUtils.isBase64Image(base64Data)) { @@ -165,7 +169,9 @@ public class MemberServiceImpl extends ServiceImpl impleme String base64Str = StringUtils.substringAfter(base64Data, ","); byte[] imageBytes = Base64.getDecoder().decode(base64Str); - String suffix = base64Data.substring(11, base64Data.indexOf(";")); + String suffix = base64Data.substring(11, base64Data.indexOf(";")).toLowerCase(); + Assert.isTrue(sysProperties.getUpload().getImage().contains(suffix), SysErrorCode.UPLOAD_FILE_TYPE_LIMIT::exception); + String path = "avatar/" + memberId + "." + suffix; FileUtils.writeByteArrayToFile(new File(MemberConfig.getUploadDir() + path), imageBytes); diff --git a/chestnut-modules/chestnut-meta/pom.xml b/chestnut-modules/chestnut-meta/pom.xml index 295891db..68bd27a6 100644 --- a/chestnut-modules/chestnut-meta/pom.xml +++ b/chestnut-modules/chestnut-meta/pom.xml @@ -6,7 +6,7 @@ chestnut-modules com.chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-modules/chestnut-monitor/pom.xml b/chestnut-modules/chestnut-monitor/pom.xml index 3a321093..8aa4ec5c 100644 --- a/chestnut-modules/chestnut-monitor/pom.xml +++ b/chestnut-modules/chestnut-monitor/pom.xml @@ -6,7 +6,7 @@ com.chestnut chestnut-modules - 1.5.5 + 1.5.6 chestnut-monitor diff --git a/chestnut-modules/chestnut-search/pom.xml b/chestnut-modules/chestnut-search/pom.xml index 43ff50c9..cf769c98 100644 --- a/chestnut-modules/chestnut-search/pom.xml +++ b/chestnut-modules/chestnut-search/pom.xml @@ -3,7 +3,7 @@ chestnut-modules com.chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-modules/chestnut-stat/pom.xml b/chestnut-modules/chestnut-stat/pom.xml index ec75ad7d..60ba6b91 100644 --- a/chestnut-modules/chestnut-stat/pom.xml +++ b/chestnut-modules/chestnut-stat/pom.xml @@ -6,7 +6,7 @@ chestnut-modules com.chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-modules/chestnut-system/pom.xml b/chestnut-modules/chestnut-system/pom.xml index 42fbd9d8..64592457 100644 --- a/chestnut-modules/chestnut-system/pom.xml +++ b/chestnut-modules/chestnut-system/pom.xml @@ -5,7 +5,7 @@ chestnut-modules com.chestnut - 1.5.5 + 1.5.6 4.0.0 @@ -53,6 +53,11 @@ chestnut-common-storage + + com.chestnut + chestnut-captcha + + de.codecentric spring-boot-admin-starter-client diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/I18nMessageSource.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/I18nMessageSource.java index eee82631..2bc992e8 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/I18nMessageSource.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/I18nMessageSource.java @@ -15,24 +15,22 @@ */ package com.chestnut.system.config; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.text.MessageFormat; -import java.util.Locale; -import java.util.Objects; - +import com.chestnut.common.redis.RedisCache; +import com.chestnut.common.utils.ConvertUtils; +import com.chestnut.common.utils.StringUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.support.AbstractMessageSource; -import com.chestnut.common.redis.RedisCache; -import com.chestnut.common.utils.ConvertUtils; -import com.chestnut.common.utils.StringUtils; -import com.chestnut.system.service.ISysI18nDictService; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.List; +import java.util.Locale; +import java.util.Objects; @Getter @Setter @@ -43,7 +41,7 @@ public class I18nMessageSource extends AbstractMessageSource implements Initiali private final RedisCache redisCache; - private String basename; + private List basename; private Charset encoding = StandardCharsets.UTF_8; diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/SystemConfig.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/SystemConfig.java index 886aea9b..35e22dcb 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/SystemConfig.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/SystemConfig.java @@ -45,7 +45,7 @@ public class SystemConfig implements WebMvcConfigurer { public SystemConfig(SysProperties properties) { UPLOAD_DIRECTORY = properties.getUploadPath(); if (StringUtils.isEmpty(UPLOAD_DIRECTORY)) { - UPLOAD_DIRECTORY = SpringUtils.getAppParentDirectory() + "/profile/"; + UPLOAD_DIRECTORY = SpringUtils.getAppParentDirectory() + SysConstants.RESOURCE_PREFIX; } UPLOAD_DIRECTORY = StringUtils.appendIfMissing(FileExUtils.normalizePath(UPLOAD_DIRECTORY), "/"); FileExUtils.mkdirs(UPLOAD_DIRECTORY); diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/converter/DictConverter.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/converter/DictConverter.java index cd26e96b..97026f7f 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/converter/DictConverter.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/converter/DictConverter.java @@ -18,12 +18,12 @@ package com.chestnut.system.config.converter; import java.util.Objects; import java.util.Optional; -import com.alibaba.excel.converters.Converter; -import com.alibaba.excel.enums.CellDataTypeEnum; -import com.alibaba.excel.metadata.GlobalConfiguration; -import com.alibaba.excel.metadata.data.ReadCellData; -import com.alibaba.excel.metadata.data.WriteCellData; -import com.alibaba.excel.metadata.property.ExcelContentProperty; +import cn.idev.excel.converters.Converter; +import cn.idev.excel.enums.CellDataTypeEnum; +import cn.idev.excel.metadata.GlobalConfiguration; +import cn.idev.excel.metadata.data.ReadCellData; +import cn.idev.excel.metadata.data.WriteCellData; +import cn.idev.excel.metadata.property.ExcelContentProperty; import com.chestnut.common.i18n.I18nUtils; import com.chestnut.common.utils.SpringUtils; import com.chestnut.common.utils.StringUtils; diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/properties/SysProperties.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/properties/SysProperties.java index a31745fa..df46113d 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/properties/SysProperties.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/properties/SysProperties.java @@ -15,10 +15,11 @@ */ package com.chestnut.system.config.properties; -import org.springframework.boot.context.properties.ConfigurationProperties; - import lombok.Getter; import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; @Getter @Setter @@ -30,11 +31,6 @@ public class SysProperties { */ private String uploadPath; - /** - * 后台登录注册验证码类型配置 - */ - private String captchaType; - /** 演示模式开关 */ private boolean demoMode; @@ -42,4 +38,21 @@ public class SysProperties { * 是否记录定时任务日志到数据库 */ private boolean scheduleLog; + + /** + * 可上传文件类型 + */ + private FileTypes upload; + + @Getter + @Setter + public static class FileTypes { + private List image = List.of("jpg", "jpeg", "png", "gif", "webp"); + private List video = List.of("mp4", "mpg", "mpeg", "rmvb", "rm", "avi", "wmv", "mov", "flv"); + private List audio = List.of("mp3", "wav", "wma", "ogg", "aiff", "aac", "flac", "mid"); + private List file = List.of("jpg", "jpeg", "png", "gif", "webp", "bmp", + "psd", "ai", "tif", "tiff", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf", "fla", "swf", "js", "css", + "shtml", "html", "htm", "txt", "ttf", "eot", "mp4", "avi", "rmvb", "mpg", "flv", "mpeg", "rm", "mov", "wmv", + "wmp", "mp3", "wma", "wav", "ogg", "rar", "zip", "gz", "bz2", "z", "iso", "cab", "jar"); + } } diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysLoginController.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysLoginController.java index 0f53d3ae..889caf95 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysLoginController.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysLoginController.java @@ -72,8 +72,7 @@ public class SysLoginController extends BaseRestController { @PostMapping("/login") public R login(@Validated @RequestBody LoginBody loginBody) { // 生成令牌 - String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), - loginBody.getUuid()); + String token = loginService.login(loginBody); return R.ok(token); } diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysProfileController.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysProfileController.java index 32b85712..3ecac1be 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysProfileController.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysProfileController.java @@ -36,7 +36,6 @@ import com.chestnut.system.domain.SysUser; import com.chestnut.system.domain.vo.DashboardUserVO; import com.chestnut.system.domain.vo.ShortcutVO; import com.chestnut.system.domain.vo.UserProfileVO; -import com.chestnut.system.enums.MenuType; import com.chestnut.system.security.AdminUserType; import com.chestnut.system.security.StpAdminUtil; import com.chestnut.system.service.*; @@ -68,8 +67,6 @@ public class SysProfileController extends BaseRestController { private final ISysMenuService menuService; - private final ISysPermissionService permissionService; - @Priv(type = AdminUserType.TYPE) @GetMapping public R profile() { @@ -95,6 +92,7 @@ public class SysProfileController extends BaseRestController { SysUser sysUser = (SysUser) loginUser.getUser(); sysUser.setNickName(user.getNickName()); + sysUser.setRealName(user.getRealName()); sysUser.setPhoneNumber(user.getPhoneNumber()); sysUser.setEmail(user.getEmail()); sysUser.setSex(user.getSex()); diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysUserController.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysUserController.java index 4c24fa68..e8d35c17 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysUserController.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysUserController.java @@ -15,7 +15,7 @@ */ package com.chestnut.system.controller; -import com.alibaba.excel.EasyExcel; +import cn.idev.excel.EasyExcel; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.chestnut.common.domain.R; diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/common/CaptchaController.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/common/CaptchaController.java index d44dcad1..04a0d78a 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/common/CaptchaController.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/common/CaptchaController.java @@ -15,88 +15,55 @@ */ package com.chestnut.system.controller.common; -import com.chestnut.common.captcha.CaptchaType; -import com.chestnut.common.config.CaptchaConfig; +import com.chestnut.common.captcha.CaptchaCheckResult; +import com.chestnut.common.captcha.CaptchaData; +import com.chestnut.common.captcha.CaptchaService; +import com.chestnut.common.captcha.ICaptchaType; import com.chestnut.common.domain.R; -import com.chestnut.common.exception.GlobalException; -import com.chestnut.common.utils.Assert; -import com.chestnut.common.utils.IdUtils; -import com.chestnut.system.SysConstants; -import com.chestnut.system.config.properties.SysProperties; -import com.chestnut.system.domain.vo.ImageCaptchaVO; -import com.chestnut.system.exception.SysErrorCode; +import com.chestnut.common.security.web.BaseRestController; +import com.chestnut.system.annotation.IgnoreDemoMode; import com.chestnut.system.fixed.config.SysCaptchaEnable; -import com.chestnut.system.monitor.CaptchaMonitoredCache; -import com.google.code.kaptcha.Producer; -import jakarta.servlet.http.HttpServletResponse; +import com.chestnut.system.fixed.config.SysCaptchaType; import lombok.RequiredArgsConstructor; -import org.springframework.util.FastByteArrayOutputStream; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.util.Base64; import java.util.Map; -import java.util.concurrent.TimeUnit; /** * 验证码操作处理 */ @RestController +@RequestMapping("/captcha") @RequiredArgsConstructor -public class CaptchaController { +public class CaptchaController extends BaseRestController { - private final Map captchaProducers; + private final CaptchaService captchaService; - private final CaptchaMonitoredCache captchaCache; - private final SysProperties properties; + @GetMapping("/get") + public R getCaptcha(@RequestParam String type) { + ICaptchaType captchaType = captchaService.getCaptchaType(type); + Object o = captchaType.create(new CaptchaData(type)); + return R.ok(o); + } - /** - * 生成验证码 - */ - @GetMapping("/captchaImage") - public R getCode(HttpServletResponse response) throws IOException { - boolean captchaEnabled = SysCaptchaEnable.isEnable(); - if (!captchaEnabled) { - return R.ok(ImageCaptchaVO.builder().captchaEnabled(false).build()); - } + @IgnoreDemoMode + @PostMapping("/check") + public R checkCaptcha(@RequestParam String type, @RequestBody CaptchaData captchaData) { + ICaptchaType captchaType = captchaService.getCaptchaType(type); + try { + CaptchaCheckResult result = captchaType.check(captchaData); + return R.ok(result); + } catch (Exception e) { + return R.ok(CaptchaCheckResult.fail()); + } + } - // 保存验证码信息 - String uuid = IdUtils.simpleUUID(); - String verifyKey = SysConstants.CAPTCHA_CODE_KEY + 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("Unkown captcha type: " + captchaType); - } - - captchaCache.setCache(verifyKey, code, SysConstants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); - // 转换流信息写出 - 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()); - } + @GetMapping("/config") + public R getLoginCaptchaConfig() { + return R.ok(Map.of( + "type", SysCaptchaType.getValue(), + "enabled", SysCaptchaEnable.isEnable() + )); } } diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysConfig.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysConfig.java index 3e11aeca..334df49a 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysConfig.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysConfig.java @@ -15,7 +15,7 @@ */ package com.chestnut.system.domain; -import com.alibaba.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysDictData.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysDictData.java index 1dd8653f..c9b9f1fd 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysDictData.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysDictData.java @@ -15,7 +15,7 @@ */ package com.chestnut.system.domain; -import com.alibaba.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysDictType.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysDictType.java index cb5aa505..14cd074c 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysDictType.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysDictType.java @@ -15,7 +15,7 @@ */ package com.chestnut.system.domain; -import com.alibaba.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysLogininfor.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysLogininfor.java index a4495b71..54146271 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysLogininfor.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysLogininfor.java @@ -20,8 +20,8 @@ import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; -import com.alibaba.excel.annotation.ExcelIgnore; -import com.alibaba.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.ExcelIgnore; +import cn.idev.excel.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysOperLog.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysOperLog.java index 2c5b6d4a..208ac1b9 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysOperLog.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysOperLog.java @@ -20,8 +20,8 @@ import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; -import com.alibaba.excel.annotation.ExcelIgnore; -import com.alibaba.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.ExcelIgnore; +import cn.idev.excel.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysPost.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysPost.java index fb189e44..143461e2 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysPost.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysPost.java @@ -15,8 +15,8 @@ */ package com.chestnut.system.domain; -import com.alibaba.excel.annotation.ExcelIgnore; -import com.alibaba.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.ExcelIgnore; +import cn.idev.excel.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysRole.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysRole.java index f0d8edbc..28b4c3cc 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysRole.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysRole.java @@ -15,7 +15,7 @@ */ package com.chestnut.system.domain; -import com.alibaba.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysSecurityConfig.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysSecurityConfig.java index ca0a671d..bbaa8df5 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysSecurityConfig.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysSecurityConfig.java @@ -107,7 +107,7 @@ public class SysSecurityConfig extends BaseEntity { /** * 密码重试安全策略
- * @see com.chestnut.system.fixed.dict.PasswordRetryStrategy.auth.enums.PasswordErrStrategy + * @see com.chestnut.system.fixed.dict.PasswordRetryStrategy */ private String passwordRetryStrategy; diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysUser.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysUser.java index 80e354dd..2a487a71 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysUser.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/SysUser.java @@ -20,9 +20,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import com.alibaba.excel.annotation.ExcelIgnore; -import com.alibaba.excel.annotation.ExcelProperty; -import com.alibaba.excel.annotation.write.style.ColumnWidth; +import cn.idev.excel.annotation.ExcelIgnore; +import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.write.style.ColumnWidth; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/dto/LoginBody.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/dto/LoginBody.java index f1428162..0df21894 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/dto/LoginBody.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/dto/LoginBody.java @@ -15,8 +15,11 @@ */ package com.chestnut.system.domain.dto; +import com.chestnut.common.captcha.CaptchaData; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import lombok.Setter; /** * 用户登录对象 @@ -24,60 +27,25 @@ import jakarta.validation.constraints.Pattern; * @author 兮玥 * @email 190785909@qq.com */ +@Getter +@Setter public class LoginBody { /** * 用户名 */ - @NotBlank - @Pattern(regexp = "^[A-Za-z0-9_]+$", message = "用户名只能使用大小写字母、数字、下划线组合") + @NotBlank(message = "{VALIDATOR.SYSTEM.INVALID_UNAME}") + @Pattern(regexp = "^[A-Za-z0-9_]+$", message = "{VALIDATOR.SYSTEM.INVALID_UNAME}") private String username; /** * 用户密码 */ - @NotBlank + @NotBlank(message = "{VALIDATOR.SYSTEM.INVALID_PWD}") private String password; /** - * 验证码 + * 验证码信息 */ - private String code; - - /** - * 唯一标识 - */ - private String uuid; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getCode() { - return code; - } - - public void setCode(String code) { - this.code = code; - } - - public String getUuid() { - return uuid; - } - - public void setUuid(String uuid) { - this.uuid = uuid; - } + private CaptchaData captcha; } diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/dto/UserImportData.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/dto/UserImportData.java index 5054e87f..a1d2615c 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/dto/UserImportData.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/dto/UserImportData.java @@ -17,7 +17,7 @@ package com.chestnut.system.domain.dto; import java.util.Set; -import com.alibaba.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.ExcelProperty; import com.chestnut.common.utils.poi.converter.StringToSetConverter; import com.chestnut.system.annotation.ExcelDictField; import com.chestnut.system.config.converter.DictConverter; diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/vo/SecurityCheckVO.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/vo/SecurityCheckVO.java index 3c0bfb2e..f6b8a556 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/vo/SecurityCheckVO.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/vo/SecurityCheckVO.java @@ -15,17 +15,9 @@ */ package com.chestnut.system.domain.vo; -import com.alibaba.excel.annotation.ExcelProperty; -import com.chestnut.common.utils.poi.converter.StringToSetConverter; -import com.chestnut.system.annotation.ExcelDictField; -import com.chestnut.system.config.converter.DictConverter; -import com.chestnut.system.fixed.dict.UserStatus; -import jakarta.validation.constraints.NotEmpty; import lombok.Getter; import lombok.Setter; -import java.util.Set; - @Setter @Getter public class SecurityCheckVO { diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/fixed/config/SysCaptchaType.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/fixed/config/SysCaptchaType.java new file mode 100644 index 00000000..7ea072fc --- /dev/null +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/fixed/config/SysCaptchaType.java @@ -0,0 +1,45 @@ +/* + * 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.system.fixed.config; + +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.chestnut.common.captcha.math.MathCaptchaType; +import com.chestnut.common.utils.SpringUtils; +import com.chestnut.system.fixed.FixedConfig; +import com.chestnut.system.service.ISysConfigService; +import org.springframework.stereotype.Component; + +@Component(FixedConfig.BEAN_PREFIX + SysCaptchaType.ID) +public class SysCaptchaType extends FixedConfig { + + public static final String ID = "SysCaptchaType"; + + private static final String DEFAULT_VALUE = MathCaptchaType.TYPE; + + private static final ISysConfigService configService = SpringUtils.getBean(ISysConfigService.class); + + public SysCaptchaType() { + super(ID, "{CONFIG." + ID + "}", DEFAULT_VALUE, null); + } + + public static String getValue() { + String configValue = configService.selectConfigByKey(ID); + if (StringUtils.isEmpty(configValue)) { + return DEFAULT_VALUE; + } + return configValue; + } +} diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/intercepter/DemoModeInterceptor.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/intercepter/DemoModeInterceptor.java index a580b4f4..6b1d683c 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/intercepter/DemoModeInterceptor.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/intercepter/DemoModeInterceptor.java @@ -43,6 +43,9 @@ public class DemoModeInterceptor implements HandlerInterceptor { if (StpAdminUtil.isLogin() && SecurityUtils.isSuperAdmin(StpAdminUtil.getLoginIdAsLong())) { return true; // 超级管理员允许操作 } + if (StpAdminUtil.isLogin() && StpAdminUtil.getRoleList(StpAdminUtil.getLoginId()).contains("admin")) { + return true; // admin角色允许操作 + } // 非Get且无忽略演示模式注解则抛出异常 if (HttpMethod.POST.matches(method) || HttpMethod.PUT.matches(method) || HttpMethod.DELETE.matches(method)) { if (handler instanceof HandlerMethod handlerMethod) { diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/monitor/CaptchaMonitoredCache.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/monitor/CaptchaMonitoredCache.java deleted file mode 100644 index 07f0d962..00000000 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/monitor/CaptchaMonitoredCache.java +++ /dev/null @@ -1,67 +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.system.monitor; - -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; - -/** - * CaptchaMonitoredCache - * - * @author 兮玥 - * @email 190785909@qq.com - */ -@Component(IMonitoredCache.BEAN_PREFIX + CaptchaMonitoredCache.ID) -@RequiredArgsConstructor -public class CaptchaMonitoredCache implements IMonitoredCache { - - public static final String ID = "Captcha"; - - private final RedisCache redisCache; - - @Override - public String getId() { - return ID; - } - - @Override - public String getCacheName() { - return "{MONITORED.CACHE.CAPTCHA}"; - } - - @Override - public String getCacheKey() { - return SysConstants.CAPTCHA_CODE_KEY; - } - - @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); - } -} diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/security/SysLoginService.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/security/SysLoginService.java index c6cbf75d..75ce269e 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/security/SysLoginService.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/security/SysLoginService.java @@ -16,6 +16,9 @@ package com.chestnut.system.security; import cn.dev33.satoken.session.SaSession; +import com.chestnut.common.captcha.CaptchaData; +import com.chestnut.common.captcha.CaptchaService; +import com.chestnut.common.captcha.ICaptchaType; import com.chestnut.common.security.SecurityUtils; import com.chestnut.common.security.domain.LoginUser; import com.chestnut.common.security.enums.DeviceType; @@ -23,14 +26,13 @@ import com.chestnut.common.utils.Assert; import com.chestnut.common.utils.IP2RegionUtils; import com.chestnut.common.utils.ServletUtils; import com.chestnut.common.utils.StringUtils; -import com.chestnut.system.SysConstants; import com.chestnut.system.domain.SysUser; +import com.chestnut.system.domain.dto.LoginBody; import com.chestnut.system.exception.SysErrorCode; import com.chestnut.system.fixed.config.SysCaptchaEnable; import com.chestnut.system.fixed.dict.LoginLogType; import com.chestnut.system.fixed.dict.SuccessOrFail; import com.chestnut.system.fixed.dict.UserStatus; -import com.chestnut.system.monitor.CaptchaMonitoredCache; import com.chestnut.system.service.*; import eu.bitwalker.useragentutils.UserAgent; import lombok.RequiredArgsConstructor; @@ -51,8 +53,6 @@ import java.util.Set; @RequiredArgsConstructor public class SysLoginService { - private final CaptchaMonitoredCache captchaCache; - private final ISysDeptService deptService; private final ISysUserService userService; @@ -63,22 +63,21 @@ public class SysLoginService { private final ISysPermissionService permissionService; + private final CaptchaService captchaService; + /** * 登录验证 * - * @param username 用户名 - * @param password 密码 - * @param code 验证码 - * @param uuid 唯一标识 + * @param loginBody 用户名 * @return 结果 */ - public String login(String username, String password, String code, String uuid) { + public String login(LoginBody loginBody) { // 验证码开关 if (SysCaptchaEnable.isEnable()) { - validateCaptcha(username, code, uuid); + validateCaptcha(loginBody.getUsername(), loginBody.getCaptcha()); } // 查找用户 - SysUser user = this.userService.lambdaQuery().eq(SysUser::getUserName, username).one(); + SysUser user = this.userService.lambdaQuery().eq(SysUser::getUserName, loginBody.getUsername()).one(); if (Objects.isNull(user)) { throw SysErrorCode.USER_NOT_EXISTS.exception(); } @@ -89,7 +88,7 @@ public class SysLoginService { throw SysErrorCode.USER_DISABLED.exception(); } // 密码校验 - if (!SecurityUtils.matches(password, user.getPassword())) { + if (!SecurityUtils.matches(loginBody.getPassword(), user.getPassword())) { // 密码错误处理策略 this.securityConfigService.processLoginPasswordError(user); if (user.isModified()) { @@ -140,27 +139,17 @@ public class SysLoginService { * 校验验证码 * * @param username 用户名 - * @param code 验证码 - * @param uuid 唯一标识 + * @param captcha 验证码 */ - public void validateCaptcha(String username, String code, String uuid) { - Assert.notEmpty(uuid, SysErrorCode.CAPTCHA_ERR::exception); + public void validateCaptcha(String username, CaptchaData captcha) { + Assert.notNull(captcha, SysErrorCode.CAPTCHA_ERR::exception); - String cacheKey = SysConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, StringUtils.EMPTY); - String captcha = captchaCache.getCache(cacheKey); - // 过期判断 - Assert.notNull(captcha, () -> { - this.logininfoService.recordLogininfor(AdminUserType.TYPE, null, username, - LoginLogType.LOGIN, SuccessOrFail.FAIL, SysErrorCode.CAPTCHA_EXPIRED.name()); - return SysErrorCode.CAPTCHA_EXPIRED.exception(); - }); - // 未过期移除缓存 - captchaCache.deleteCache(cacheKey); - // 判断是否与输入验证码一致 - Assert.isTrue(StringUtils.equals(code, captcha), () -> { + ICaptchaType captchaType = captchaService.getCaptchaType(captcha.getType()); + boolean validated = captchaType.isTokenValidated(captcha); + if (!validated) { this.logininfoService.recordLogininfor(AdminUserType.TYPE, null, username, LoginLogType.LOGIN, SuccessOrFail.FAIL, SysErrorCode.CAPTCHA_ERR.name()); - return SysErrorCode.CAPTCHA_ERR.exception(); - }); + throw SysErrorCode.CAPTCHA_ERR.exception(); + } } } diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/security/SysRegisterService.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/security/SysRegisterService.java index 25f91cb3..002d8174 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/security/SysRegisterService.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/security/SysRegisterService.java @@ -15,9 +15,11 @@ */ package com.chestnut.system.security; +import com.chestnut.common.captcha.CaptchaData; +import com.chestnut.common.captcha.CaptchaService; +import com.chestnut.common.captcha.ICaptchaType; import com.chestnut.common.utils.Assert; import com.chestnut.common.utils.StringUtils; -import com.chestnut.system.SysConstants; import com.chestnut.system.domain.SysUser; import com.chestnut.system.domain.dto.RegisterBody; import com.chestnut.system.exception.SysErrorCode; @@ -25,14 +27,11 @@ import com.chestnut.system.fixed.config.SysCaptchaEnable; import com.chestnut.system.fixed.config.SysRegistEnable; import com.chestnut.system.fixed.dict.LoginLogType; import com.chestnut.system.fixed.dict.SuccessOrFail; -import com.chestnut.system.monitor.CaptchaMonitoredCache; import com.chestnut.system.service.ISysLogininforService; import com.chestnut.system.service.ISysUserService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import java.util.Objects; - /** * 注册校验方法 * @@ -45,10 +44,10 @@ public class SysRegisterService { private final ISysUserService userService; - private final CaptchaMonitoredCache captchaCache; - private final ISysLogininforService logininfoService; + private final CaptchaService captchaService; + /** * 注册 */ @@ -56,7 +55,7 @@ public class SysRegisterService { Assert.isTrue(SysRegistEnable.isEnable(), SysErrorCode.REGIST_DISABELD::exception); // 验证码开关 if (SysCaptchaEnable.isEnable()) { - validateCaptcha(registerBody.getUsername(), registerBody.getCode(), registerBody.getUuid()); + validateCaptcha(registerBody.getUsername(), registerBody.getCaptcha()); } SysUser user = new SysUser(); user.setUserName(registerBody.getUsername()); @@ -71,25 +70,16 @@ public class SysRegisterService { * 校验验证码 * * @param username 用户名 - * @param code 验证码 - * @param uuid 唯一标识 - * @return 结果 + * @param captchaData 验证码 */ - public void validateCaptcha(String username, String code, String uuid) { - if (StringUtils.isBlank(uuid)) { + public void validateCaptcha(String username, CaptchaData captchaData) { + Assert.notNull(captchaData, SysErrorCode.CAPTCHA_ERR::exception); + ICaptchaType captchaType = captchaService.getCaptchaType(captchaData.getType()); + boolean validated = captchaType.isTokenValidated(captchaData); + if (!validated) { + this.logininfoService.recordLogininfor(AdminUserType.TYPE, null, username, + LoginLogType.REGIST, SuccessOrFail.FAIL, SysErrorCode.CAPTCHA_ERR.name()); throw SysErrorCode.CAPTCHA_ERR.exception(); } - String verifyKey = SysConstants.CAPTCHA_CODE_KEY + uuid; - try { - String captcha = captchaCache.getCache(verifyKey); - if (Objects.isNull(captcha)) { - throw SysErrorCode.CAPTCHA_EXPIRED.exception(); - } - if (StringUtils.equalsIgnoreCase(code, captcha)) { - throw SysErrorCode.CAPTCHA_ERR.exception(); - } - } finally { - captchaCache.deleteCache(verifyKey); - } } } diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/ISysUserService.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/ISysUserService.java index 5a671128..b96335c2 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/ISysUserService.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/ISysUserService.java @@ -15,12 +15,11 @@ */ package com.chestnut.system.service; -import java.util.List; - -import org.springframework.web.multipart.MultipartFile; - import com.baomidou.mybatisplus.extension.service.IService; import com.chestnut.system.domain.SysUser; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; /** * 用户 业务层 @@ -29,75 +28,67 @@ public interface ISysUserService extends IService { /** * 根据用户ID查询用户所属角色组 - * - * @param userName - * 用户名 + * + * @param userId 用户ID * @return 结果 */ - public String selectUserRoleGroup(Long userId); + String selectUserRoleGroup(Long userId); /** * 根据用户ID查询用户所属岗位组 - * - * @param userName - * 用户名 + * + * @param userId 用户ID * @return 结果 */ - public String selectUserPostGroup(Long userId); + String selectUserPostGroup(Long userId); /** * 校验用户名称是否唯一 - * - * @param user - * 用户信息 + * + * @param username 用户名 + * @param userId 用户ID * @return 结果 */ - public boolean checkUserNameUnique(String username, Long userId); + boolean checkUserNameUnique(String username, Long userId); /** * 校验手机号码是否唯一 * - * @param user - * 用户信息 + * @param phoneNumber 手机号 + * @param userId 用户ID * @return 结果 */ - public boolean checkPhoneUnique(String phoneNumber, Long userId); + boolean checkPhoneUnique(String phoneNumber, Long userId); /** * 校验email是否唯一 * - * @param user - * 用户信息 + * @param email email + * @param userId 用户ID * @return 结果 */ - public boolean checkEmailUnique(String email, Long userId); + boolean checkEmailUnique(String email, Long userId); /** * 新增用户信息 * - * @param user - * 用户信息 - * @return 结果 + * @param user 用户信息 */ - public void insertUser(SysUser user); + void insertUser(SysUser user); /** * 注册用户信息 * - * @param user - * 用户信息 - * @return 结果 + * @param user 用户信息 */ - public void registerUser(SysUser user); + void registerUser(SysUser user); /** * 修改用户信息 * - * @param user - * 用户信息 - * @return 结果 + * @param user 用户信息 */ - public void updateUser(SysUser user); + void updateUser(SysUser user); /** * 用户授权角色 @@ -107,25 +98,21 @@ public interface ISysUserService extends IService { * @param roleIds * 角色组 */ - public void insertUserAuth(Long userId, List roleIds); + void insertUserAuth(Long userId, List roleIds); /** * 重置用户密码 * - * @param user - * 用户信息 - * @return 结果 + * @param user 用户信息 */ - public void resetPwd(SysUser user); + void resetPwd(SysUser user); /** * 批量删除用户信息 * - * @param userIds - * 需要删除的用户ID - * @return 结果 + * @param userIds 需要删除的用户ID */ - public void deleteUserByIds(List userIds); + void deleteUserByIds(List userIds); /** * 解锁用户 @@ -135,9 +122,9 @@ public interface ISysUserService extends IService { /** * 上传用户头像 * - * @param userId - * @param file + * @param userId 用户ID + * @param file 头像文件 * @return 头像文件相对路径 */ - public String uploadAvatar(Long userId, MultipartFile file); + String uploadAvatar(Long userId, MultipartFile file); } diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/impl/SysI18nDictServiceImpl.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/impl/SysI18nDictServiceImpl.java index 768363d2..6589b309 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/impl/SysI18nDictServiceImpl.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/impl/SysI18nDictServiceImpl.java @@ -39,7 +39,10 @@ import org.springframework.util.CollectionUtils; import java.io.IOException; import java.io.InputStream; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.stream.Collectors; @Slf4j @@ -50,6 +53,8 @@ public class SysI18nDictServiceImpl extends ServiceImpl lines = IOUtils.readLines(is, messageSource.getEncoding()); - lines.stream() - .filter(s -> StringUtils.isNotEmpty(s) && s.indexOf("=") > 0) - .forEach(s -> { - redisCache.setCacheMapValue(cacheKey, - StringUtils.substringBefore(s, "="), - StringUtils.substringAfter(s, "=")); - }); + for (String basename : messageSource.getBasename()) { + String target = basename.replace('.', '/'); + Resource[] resources = new PathMatchingResourcePatternResolver(this.getClass().getClassLoader()) + .getResources("classpath*:" + target + "*.properties"); + for (Resource resource : resources) { + String langTag = messageSource.getDefaultLocale().toLanguageTag(); + String filename = resource.getFilename(); + if (filename.contains("_")) { + langTag = StringUtils.substring(resource.getFilename(), + filename.indexOf("_") + 1, filename.lastIndexOf(".")); + langTag = StringUtils.replace(langTag, "_", "-"); + } + String cacheKey = CACHE_PREFIX + langTag; + try (InputStream is = resource.getInputStream()) { + List lines = IOUtils.readLines(is, messageSource.getEncoding()); + lines.stream() + .filter(s -> StringUtils.isNotEmpty(s) && s.contains(PROPERTY_SPLITERATOR)) + .forEach(s -> { + String[] kv = s.split(PROPERTY_SPLITERATOR); + redisCache.setCacheMapValue(cacheKey, kv[0], kv[1]); + }); + } } } } diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/impl/SysUserServiceImpl.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/impl/SysUserServiceImpl.java index 01bbb22a..aa36e3d5 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/impl/SysUserServiceImpl.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/impl/SysUserServiceImpl.java @@ -15,8 +15,8 @@ */ package com.chestnut.system.service.impl; -import com.alibaba.excel.context.AnalysisContext; -import com.alibaba.excel.read.listener.ReadListener; +import cn.idev.excel.context.AnalysisContext; +import cn.idev.excel.read.listener.ReadListener; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.chestnut.common.exception.CommonErrorCode; @@ -224,6 +224,7 @@ public class SysUserServiceImpl extends ServiceImpl impl String oldStatus = db.getStatus(); db.setNickName(user.getNickName()); + db.setRealName(user.getRealName()); db.setDeptId(user.getDeptId()); db.setPhoneNumber(user.getPhoneNumber()); db.setEmail(user.getEmail()); diff --git a/chestnut-modules/chestnut-system/src/main/resources/i18n/messages.properties b/chestnut-modules/chestnut-system/src/main/resources/i18n/messages.properties index a241bb6d..9d8d6dfb 100644 --- a/chestnut-modules/chestnut-system/src/main/resources/i18n/messages.properties +++ b/chestnut-modules/chestnut-system/src/main/resources/i18n/messages.properties @@ -3,6 +3,8 @@ VALIDATOR.SYSTEM.INVALID_DICT_VALUE=字典【{0}】数据非法:{1} VALIDATOR.SYSTEM.USER_GENDER=用户性别输入错误:{0} VALIDATOR.SYSTEM.INVALID_LONG_ID=长整形ID参数值错误:{0} VALIDATOR.SYSTEM.SCRIPT_TEXT=Groovy脚本不能为空 +VALIDATOR.SYSTEM.INVALID_UNAME=用户名不能为空且只能使用大小写字母、数字、下划线组合 +VALIDATOR.SYSTEM.INVALID_PWD=密码不能为空 #错误消息 ERR.SYS.UNAME_PWD_REQUIRED=账号/密码不能为空 @@ -107,6 +109,7 @@ DICT.SecurityPasswordRetryStrategy.LOCK=锁定账号,拒绝登录一段时间 # 固定配置参数 CONFIG.SysCaptchaEnable=后台账号验证码开关 +CONFIG.SysCaptchaType=后台账号验证码类型 CONFIG.SysRegistEnable=后台账号注册开关 CONFIG.SysUploadSizeLimit=上传文件大小 CONFIG.SysUploadTypeLimit=上传文件类型 diff --git a/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_en.properties b/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_en.properties index a95bb3d4..a89a1fd7 100644 --- a/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_en.properties +++ b/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_en.properties @@ -2,6 +2,8 @@ VALIDATOR.SYSTEM.INVALID_DICT_VALUE=Invalid dict value "{1}" for [{0}] VALIDATOR.SYSTEM.USER_GENDER=Invalid user gender value: {0} VALIDATOR.SYSTEM.INVALID_LONG_ID=Invalid long id value: {0} VALIDATOR.SYSTEM.SCRIPT_TEXT=Groovy script cannot be empty. +VALIDATOR.SYSTEM.INVALID_UNAME=The username cannot be empty and only uppercase and lowercase letters, numbers, and underscores can be used. +VALIDATOR.SYSTEM.INVALID_PWD=Password cannot be empty. #错误消息 ERR.SYS.UNAME_PWD_REQUIRED=Account/Password cannot be empty. @@ -107,6 +109,7 @@ DICT.SecurityPasswordRetryStrategy.LOCK=Lock # 固定配置参数 CONFIG.SysCaptchaEnable=Account Captcha Switch +CONFIG.SysCaptchaType=Account Captcha Type CONFIG.SysRegistEnable=Account Registration Switch CONFIG.SysUploadSizeLimit=Upload File Max Size CONFIG.SysUploadTypeLimit=Upload File Type diff --git a/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_zh_TW.properties b/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_zh_TW.properties index 3afb9922..ade31303 100644 --- a/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_zh_TW.properties +++ b/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_zh_TW.properties @@ -3,6 +3,8 @@ VALIDATOR.SYSTEM.INVALID_DICT_VALUE=字典【{0}】數據非法:{1} VALIDATOR.SYSTEM.USER_GENDER=用戶性別輸入錯誤:{0} VALIDATOR.SYSTEM.INVALID_LONG_ID=長整形ID參數值錯誤:{0} VALIDATOR.SYSTEM.SCRIPT_TEXT=Groovy腳本不能為空 +VALIDATOR.SYSTEM.INVALID_UNAME=用戶名不能為空且只能使用大小寫字母、數字、下劃線組合 +VALIDATOR.SYSTEM.INVALID_PWD=密碼不能為空 #錯誤消息 ERR.SYS.UNAME_PWD_REQUIRED=賬號/密碼不能為空 @@ -107,6 +109,7 @@ DICT.SecurityPasswordRetryStrategy.LOCK=鎖定賬號,拒絕登錄一段時間 # 固定配置參數 CONFIG.SysCaptchaEnable=後台賬號驗證碼開關 +CONFIG.SysCaptchaType=後台賬號驗證碼類型 CONFIG.SysRegistEnable=後台賬號註冊開關 CONFIG.SysUploadSizeLimit=上傳檔案大小 CONFIG.SysUploadTypeLimit=上傳檔案類型 diff --git a/chestnut-modules/chestnut-vote/pom.xml b/chestnut-modules/chestnut-vote/pom.xml index 9ae9d78f..6a5fa625 100644 --- a/chestnut-modules/chestnut-vote/pom.xml +++ b/chestnut-modules/chestnut-vote/pom.xml @@ -3,7 +3,7 @@ chestnut-modules com.chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-modules/chestnut-word/pom.xml b/chestnut-modules/chestnut-word/pom.xml index 259a26ee..f745c192 100644 --- a/chestnut-modules/chestnut-word/pom.xml +++ b/chestnut-modules/chestnut-word/pom.xml @@ -6,7 +6,7 @@ chestnut-modules com.chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-modules/chestnut-word/src/main/java/com/chestnut/word/domain/TagWord.java b/chestnut-modules/chestnut-word/src/main/java/com/chestnut/word/domain/TagWord.java index 3e64bf71..d15e220a 100644 --- a/chestnut-modules/chestnut-word/src/main/java/com/chestnut/word/domain/TagWord.java +++ b/chestnut-modules/chestnut-word/src/main/java/com/chestnut/word/domain/TagWord.java @@ -19,6 +19,7 @@ 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.chestnut.common.annotation.XComment; import com.chestnut.common.db.domain.BaseEntity; import lombok.Getter; import lombok.Setter; @@ -38,37 +39,26 @@ public class TagWord extends BaseEntity { public static final String TABLE_NAME = "cc_tag_word"; + @XComment("TAG ID") @TableId(value = "word_id", type = IdType.INPUT) private Long wordId; - /** - * 所有者ID(冗余字段,与TagWordGroup.ownerId同步) - */ + @XComment("所有者ID(冗余字段,与TagWordGroup.ownerId同步)") private String owner; - /** - * 所属分组ID - */ + @XComment("所属分组ID") private Long groupId; - /** - * 名称 - */ + @XComment("TAG词") private String word; - /** - * 图片路径 - */ + @XComment("关联图片路径") private String logo; - /** - * 使用次数 - */ + @XComment("关联使用次数") private Long useCount; - /** - * 点击次数 - */ + @XComment("点击次数") private Long hitCount; /** @@ -77,8 +67,6 @@ public class TagWord extends BaseEntity { @TableField(exist = false) private String src; - /** - * 排序标识 - */ + @XComment("排序标识") private Long sortFlag; } diff --git a/chestnut-modules/chestnut-word/src/main/java/com/chestnut/word/domain/TagWordGroup.java b/chestnut-modules/chestnut-word/src/main/java/com/chestnut/word/domain/TagWordGroup.java index 345c993a..b47bb8bf 100644 --- a/chestnut-modules/chestnut-word/src/main/java/com/chestnut/word/domain/TagWordGroup.java +++ b/chestnut-modules/chestnut-word/src/main/java/com/chestnut/word/domain/TagWordGroup.java @@ -18,6 +18,7 @@ package com.chestnut.word.domain; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.chestnut.common.annotation.XComment; import com.chestnut.common.db.domain.BaseEntity; import lombok.Getter; import lombok.Setter; @@ -37,36 +38,25 @@ public class TagWordGroup extends BaseEntity { public static final String TABLE_NAME = "cc_tag_word_group"; + @XComment("标签分组ID") @TableId(value = "group_id", type = IdType.INPUT) private Long groupId; - /** - * 所有者ID(扩展用) - */ + @XComment("所有者ID(扩展用)") private String owner; - /** - * 父级ID - */ + @XComment("父级ID") private Long parentId; - /** - * 名称 - */ + @XComment("名称") private String name; - /** - * 编码,唯一标识 - */ + @XComment("编码,唯一标识") private String code; - /** - * 排序标识 - */ + @XComment("排序标识") private Long sortFlag; - /** - * TAG词数量 - */ + @XComment("TAG词数量") private Long wordTotal; } diff --git a/chestnut-modules/pom.xml b/chestnut-modules/pom.xml index 92c6b93c..1204fb17 100644 --- a/chestnut-modules/pom.xml +++ b/chestnut-modules/pom.xml @@ -5,7 +5,7 @@ com.chestnut chestnut - 1.5.5 + 1.5.6 4.0.0 diff --git a/chestnut-ui/package.json b/chestnut-ui/package.json index b453b433..3683ffd3 100644 --- a/chestnut-ui/package.json +++ b/chestnut-ui/package.json @@ -1,6 +1,6 @@ { "name": "ChestnutCMS", - "version": "1.5.5", + "version": "1.5.6", "description": "ChestnutCMS[栗子内容管理系统]", "author": "兮玥 - 190785909@qq.com", "license": "Apache-2.0", diff --git a/chestnut-ui/src/api/contentcore/exmodel.js b/chestnut-ui/src/api/contentcore/exmodel.js index a6079850..e3b3895c 100644 --- a/chestnut-ui/src/api/contentcore/exmodel.js +++ b/chestnut-ui/src/api/contentcore/exmodel.js @@ -9,10 +9,11 @@ export function listXModel(query) { }) } -export function listXModelOptions() { +export function listXModelOptions(params) { return request({ url: '/cms/exmodel/options', - method: 'get' + method: 'get', + params: params }) } diff --git a/chestnut-ui/src/api/contentcore/site.js b/chestnut-ui/src/api/contentcore/site.js index de34d682..a272935c 100644 --- a/chestnut-ui/src/api/contentcore/site.js +++ b/chestnut-ui/src/api/contentcore/site.js @@ -188,4 +188,12 @@ export function getDynamicPageTypes() { url: '/cms/dynamicPageTypes', method: 'get' }) +} + +// 首页站点信息看板数据 +export function getDashboardSiteInfo() { + return request({ + url: '/cms/site/getDashboardSiteInfo', + method: 'get' + }) } \ No newline at end of file diff --git a/chestnut-ui/src/api/login.js b/chestnut-ui/src/api/login.js index 649f59c8..03b5ca13 100644 --- a/chestnut-ui/src/api/login.js +++ b/chestnut-ui/src/api/login.js @@ -1,13 +1,7 @@ import request from '@/utils/request' // 登录方法 -export function login(username, password, code, uuid) { - const data = { - username, - password, - code, - uuid - } +export function login(data) { return request({ url: '/login', headers: { @@ -46,10 +40,10 @@ export function logout() { }) } -// 获取验证码 -export function getCodeImg() { +// 获取验证码配置 +export function getLoginCaptchaConfig() { return request({ - url: '/captchaImage', + url: '/captcha/config', headers: { isToken: false }, diff --git a/chestnut-ui/src/api/system/captcha.js b/chestnut-ui/src/api/system/captcha.js new file mode 100644 index 00000000..b5b22ef7 --- /dev/null +++ b/chestnut-ui/src/api/system/captcha.js @@ -0,0 +1,16 @@ +import request from '@/utils/request' + +export function getCaptcha(type) { + return request({ + url: '/captcha/get?type=' + type, + method: 'get', + }) +} + +export function checkCaptcha(type, data) { + return request({ + url: '/captcha/check?type=' + type, + method: 'post', + data: data + }) +} \ No newline at end of file diff --git a/chestnut-ui/src/assets/401_images/401.gif b/chestnut-ui/src/assets/error/401.gif similarity index 100% rename from chestnut-ui/src/assets/401_images/401.gif rename to chestnut-ui/src/assets/error/401.gif diff --git a/chestnut-ui/src/assets/error/403.gif b/chestnut-ui/src/assets/error/403.gif new file mode 100644 index 00000000..cd6e0d94 Binary files /dev/null and b/chestnut-ui/src/assets/error/403.gif differ diff --git a/chestnut-ui/src/assets/404_images/404.png b/chestnut-ui/src/assets/error/404.png similarity index 100% rename from chestnut-ui/src/assets/404_images/404.png rename to chestnut-ui/src/assets/error/404.png diff --git a/chestnut-ui/src/assets/404_images/404_cloud.png b/chestnut-ui/src/assets/error/404_cloud.png similarity index 100% rename from chestnut-ui/src/assets/404_images/404_cloud.png rename to chestnut-ui/src/assets/error/404_cloud.png diff --git a/chestnut-ui/src/assets/styles/chestnut.scss b/chestnut-ui/src/assets/styles/chestnut.scss index d5dd0cdc..ac07aecc 100644 --- a/chestnut-ui/src/assets/styles/chestnut.scss +++ b/chestnut-ui/src/assets/styles/chestnut.scss @@ -46,6 +46,9 @@ .ml10 { margin-left: 10px; } +.mt15 { + margin-top: 15px; +} .mt20 { margin-top: 20px; } @@ -61,6 +64,12 @@ .ml20 { margin-left: 20px; } +.text-align-center { + text-align: center; +} +.text-align-right { + text-align: right; +} .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { font-family: inherit; @@ -111,7 +120,7 @@ } .el-form-search .el-form-item { - margin-bottom: 0; + // margin-bottom: 0; } /** 表格布局 **/ diff --git a/chestnut-ui/src/assets/styles/element-ui.scss b/chestnut-ui/src/assets/styles/element-ui.scss index 363092a6..9f0fb6c6 100644 --- a/chestnut-ui/src/assets/styles/element-ui.scss +++ b/chestnut-ui/src/assets/styles/element-ui.scss @@ -89,4 +89,14 @@ > .el-submenu__title .el-submenu__icon-arrow { display: none; +} + +.message-top-right { + left: unset!important; + right: 20px!important; +} + +.message-top-left { + left: 20px!important; + right: unset!important; } \ No newline at end of file diff --git a/chestnut-ui/src/i18n/lang/en.js b/chestnut-ui/src/i18n/lang/en.js index 76e9d59e..19c61c07 100644 --- a/chestnut-ui/src/i18n/lang/en.js +++ b/chestnut-ui/src/i18n/lang/en.js @@ -109,11 +109,15 @@ export default { } }, Error: { - Err401: "401 Error!", - NoPermission: "Permission denied!", - NoPermissionTip: "Sorry, you do not have access rights. Please do not engage in illegal operations! You can return to the main page.", - GoHome: "Back Home", - Err404: "404 Error!", + Err401: "[401] Forbidden!", + Forbidden: "Unauthenticated!", + ForbiddenTip: "Sorry, you have not been authenticated yet. Please try logging in again.", + GoLogin: "Go to the login page", + Err403: "[403] Deny access!", + NoPermission: "Deny access!", + NoPermissionTip: "Sorry, you do not have access permission. Please apply for relevant resource access permission from the administrator.", + GoHome: "Go to the home page", + Err404: "[404] The access resource does not exist!", Err404Tip: "Sorry, the page you are looking for does not exist. Try checking for errors in the URL, then press the refresh button on the browser or try to find other content in our application.", PageNotFound: "Page not found!", Unknown: 'Unknown system error, please notify then administrator!' @@ -149,6 +153,7 @@ export default { Basic: 'Basic', ResetPassword: 'Reset Password', NickName: 'Nick Name', + RealName: 'Real Name', Gender: 'Gender', GenderMale: 'Male', GenderFemale: 'Female', @@ -254,6 +259,7 @@ export default { UserId: "User ID", UserName: "User Name", NickName: "Nick Name", + RealName: "Real Name", Dept: "Dept", PhoneNumber: "Phone Num", Email: "Email", @@ -287,6 +293,7 @@ export default { DeptName: "Input deptment name.", UserName: "Input user name.", NickName: "Input nick name", + RealName: "Input real name", PhoneNumber: "Input phone number.", Status: "User Status", Dept: "Select deptment", @@ -954,6 +961,9 @@ export default { PrefixMode_Absolute: "Absolute", PrefixMode_Relative: "Relative", ErrPageLink: "Error Page Link", + Dashboard: { + MissingSiteUrl: "The site domain not configured." + }, Tab: { Basic: "Basic Information", Extend: "Extend Config", @@ -991,6 +1001,7 @@ export default { ThumbnailHeight: "Thumbnail Height", ThumbnailSizeTip: "Do not generate thumbnails when width/height is 0", StorageType: "Storage Type", + StorageTip: "After changing the storage, you need to manually synchronize resource files to the new storage.", Local: "Local", AliyunOSS: "AliyunOSS", TencentCOS: "TencentCOS", @@ -1382,6 +1393,7 @@ export default { }, Block: { Basic: "Basic", + PublishPipeProp: "Publish Pipe Configuration", ManualList: "Custom List", Title: "Title", AddRow: "Add Row", @@ -1980,6 +1992,7 @@ export default { Deploy: { Cert: { Domain: "Domain", + RR: "RR", Issuer: "Authority", IssueTime: "Issue Time", ExpireTime: "Expire Time", @@ -1998,6 +2011,8 @@ export default { Applying: "Waiting for certificate application...", Authroizating: "Authorizating...", Servers: "Synchronize to server", + SyncToServer: "Sync", + Syncing: "Syncing certificate to servers...", Placeholder: { Domain: "Input domain..." } diff --git a/chestnut-ui/src/i18n/lang/zh-CN.js b/chestnut-ui/src/i18n/lang/zh-CN.js index 731894ef..b9a482a5 100644 --- a/chestnut-ui/src/i18n/lang/zh-CN.js +++ b/chestnut-ui/src/i18n/lang/zh-CN.js @@ -109,9 +109,13 @@ export default { } }, Error: { - Err401: "【401】认证失败,无法访问系统资源!", - NoPermission: "您没有访问权限!", - NoPermissionTip: "对不起,您没有访问权限,请不要进行非法操作!您可以返回主页面", + Err401: "【401】未通过认证!", + Forbidden: "未通过认证", + ForbiddenTip: "对不起,您尚未通过系统认证,请尝试重新登录。", + GoLogin: "去登录", + Err403: "【403】权限校验失败!", + NoPermission: "无访问权限!", + NoPermissionTip: "对不起,您没有访问权限,请向系统管理者申请相关资源访问权限!", GoHome: "回首页", Err404: "【404】访问资源不存在!", Err404Tip: "对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。", @@ -149,6 +153,7 @@ export default { Basic: '基础资料', ResetPassword: '修改密码', NickName: '昵称', + RealName: '真实姓名', Gender: '性别', GenderMale: '男', GenderFemale: '女', @@ -254,6 +259,7 @@ export default { UserId: "用户ID", UserName: "用户名", NickName: "昵称", + RealName: "真实姓名", Dept: "所属部门", PhoneNumber: "手机号码", Email: "邮箱", @@ -287,6 +293,7 @@ export default { DeptName: "请输入部门名称", UserName: "请输入用户名称", NickName: "请输入用户昵称", + RealName: "请输入真实姓名", PhoneNumber: "请输入手机号码", Dept: "请选择所属部门", Email: "请输入用户邮箱", @@ -954,6 +961,9 @@ export default { PrefixMode_Absolute: "绝对路径", PrefixMode_Relative: "相对路径", ErrPageLink: "错误页面", + Dashboard: { + MissingSiteUrl: "站点域名未配置!" + }, Tab: { Basic: "基础信息", Extend: "扩展配置", @@ -991,6 +1001,7 @@ export default { ThumbnailHeight: "默认缩略图高度", ThumbnailSizeTip: "缩略图宽/高为0时默认不生成缩略图", StorageType: "存储策略", + StorageTip: "更换存储策略后,原资源库的资源文件需要手动同步到新的存储库。", Local: "本地", AliyunOSS: "阿里云OSS", TencentCOS: "腾讯云COS", @@ -1382,6 +1393,7 @@ export default { }, Block: { Basic: "基础属性", + PublishPipeProp: "发布通道配置", ManualList: "自定义列表", Title: "标题", AddRow: "添加行", @@ -1980,6 +1992,7 @@ export default { Deploy: { Cert: { Domain: "域名", + RR: "主机记录", Issuer: "证书颁发机构", IssueTime: "签发时间", ExpireTime: "过期时间", @@ -1998,6 +2011,8 @@ export default { Applying: "正在提交证书申请,请稍等...", Authroizating: "正在验证,请稍等...", Servers: "同步服务器", + SyncToServer: "同步", + Syncing: "正在同步证书到远程服务器,请稍等...", Placeholder: { Domain: "输入域名查询" } diff --git a/chestnut-ui/src/i18n/lang/zh-TW.js b/chestnut-ui/src/i18n/lang/zh-TW.js index 4fdd96c7..09099fb5 100644 --- a/chestnut-ui/src/i18n/lang/zh-TW.js +++ b/chestnut-ui/src/i18n/lang/zh-TW.js @@ -109,11 +109,15 @@ export default { } }, Error: { - Err401: "【401】認證失敗,無法訪問系統資源!", - NoPermission: "您沒有訪問許可權!", - NoPermissionTip: "對不起,您沒有訪問許可權,請不要進行非法操作!您可以返回主頁面", + Err401: "【401】未通過認證!", + Forbidden: "未通過認證", + ForbiddenTip: "對不起,您尚未通過系統認證,請嘗試重新登錄。", + GoLogin: "去登錄", + Err403: "【403】權限校驗失敗!", + NoPermission: "無訪問權限!", + NoPermissionTip: "對不起,您沒有訪問權限,請向系統管理者申請相關資源訪問權限!", GoHome: "回首頁", - Err404: "【404】访问资源不存在!", + Err404: "【404】訪問資源不存在!", Err404Tip: "對不起,您正在尋找的頁面不存在。嘗試檢查URL的錯誤,然後按瀏覽器上的刷新按鈕或嘗試在我們的應用程序中找到其他內容。", PageNotFound: "找不到網頁!", Unknown: '系統未知錯誤,請反饋給管理員!' @@ -149,6 +153,7 @@ export default { Basic: '基礎資料', ResetPassword: '修改密碼', NickName: '昵稱', + RealName: '真實姓名', Gender: '性別', GenderMale: '男', GenderFemale: '女', @@ -254,6 +259,7 @@ export default { UserId: "用戶ID", UserName: "用戶名", NickName: "昵稱", + RealName: "真實姓名", Dept: "所屬部門", PhoneNumber: "手機號碼", Email: "郵箱", @@ -287,6 +293,7 @@ export default { DeptName: "請輸入部門名稱", UserName: "請輸入用戶名稱", NickName: "請輸入用戶昵稱", + RealName: "请输入真實姓名", PhoneNumber: "請輸入手機號碼", Dept: "請選擇所屬部門", Email: "請輸入用戶郵箱", @@ -954,6 +961,9 @@ export default { PrefixMode_Absolute: "絕對路徑", PrefixMode_Relative: "相對路徑", ErrPageLink: "錯誤頁面", + Dashboard: { + MissingSiteUrl: "站點域名未配置!" + }, Tab: { Basic: "基礎資訊", Extend: "擴展配置", @@ -991,6 +1001,7 @@ export default { ThumbnailHeight: "預設縮略圖高度", ThumbnailSizeTip: "縮略圖寬/高為0時預設不生成縮略圖", StorageType: "存儲策略", + StorageTip: "更換存儲策略後,原資源庫的資源文件需要手動同步到新的存儲庫。", Local: "本地", AliyunOSS: "阿里雲OSS", TencentCOS: "騰訊雲COS", @@ -1382,6 +1393,7 @@ export default { }, Block: { Basic: "基礎屬性", + PublishPipeProp: "發佈通道配置", ManualList: "自定義列表", Title: "標題", AddRow: "添加行", @@ -1980,6 +1992,7 @@ export default { Deploy: { Cert: { Domain: "域名", + RR: "主機記錄", Issuer: "證書頒發機構", IssueTime: "簽發時間", ExpireTime: "過期時間", @@ -1998,6 +2011,8 @@ export default { Applying: "正在提交證書申請,請稍等...", Authroizating: "正在驗證,請稍等...", Servers: "同步服務器", + SyncToServer: "同步", + Syncing: "正在同步證書到遠程服務器,請稍等...", Placeholder: { Domain: "輸入域名查詢" } diff --git a/chestnut-ui/src/main.js b/chestnut-ui/src/main.js index a885ae33..445e58ff 100644 --- a/chestnut-ui/src/main.js +++ b/chestnut-ui/src/main.js @@ -50,6 +50,7 @@ Vue.use(VideoPlayer) Vue.prototype.getDicts = getDicts Vue.prototype.getConfigKey = getConfigKey Vue.prototype.parseTime = tools.parseTime +Vue.prototype.tableRowNo = tools.tableRowNo Vue.prototype.resetForm = tools.resetForm Vue.prototype.addDateRange = tools.addDateRange Vue.prototype.selectDictLabel = tools.selectDictLabel diff --git a/chestnut-ui/src/permission.js b/chestnut-ui/src/permission.js index 6bb0a1f8..4db3e525 100644 --- a/chestnut-ui/src/permission.js +++ b/chestnut-ui/src/permission.js @@ -8,7 +8,7 @@ import { isRelogin } from '@/utils/request' NProgress.configure({ showSpinner: false }) -const whiteList = ['/login', '/auth-redirect', '/bind', '/register'] +const whiteList = ['/login', '/auth-redirect', '/bind', '/register', '/401'] router.beforeEach((to, from, next) => { NProgress.start() diff --git a/chestnut-ui/src/plugins/modal.js b/chestnut-ui/src/plugins/modal.js index a391390b..f27fc7ba 100644 --- a/chestnut-ui/src/plugins/modal.js +++ b/chestnut-ui/src/plugins/modal.js @@ -5,20 +5,32 @@ let loadingInstance; export default { // 消息提示 - msg(content) { - Message.info(content) + msg(content, customClass = "") { + Message.info({ + message: content, + customClass: customClass + }) }, // 错误消息 - msgError(content) { - Message.error(content) + msgError(content, customClass = "") { + Message.error({ + message: content, + customClass: customClass + }) }, // 成功消息 - msgSuccess(content) { - Message.success(content) + msgSuccess(content, customClass = "") { + Message.success({ + message: content, + customClass: customClass + }) }, // 警告消息 - msgWarning(content) { - Message.warning(content) + msgWarning(content, customClass = "") { + Message.warning({ + message: content, + customClass: customClass + }) }, // 弹出提示 alert(content) { diff --git a/chestnut-ui/src/router/index.js b/chestnut-ui/src/router/index.js index b96f53bd..6b84f59a 100644 --- a/chestnut-ui/src/router/index.js +++ b/chestnut-ui/src/router/index.js @@ -62,6 +62,11 @@ export const constantRoutes = [ component: () => import('@/views/error/401'), hidden: true }, + { + path: '/403', + component: () => import('@/views/error/403'), + hidden: true + }, { path: '', component: Layout, diff --git a/chestnut-ui/src/store/modules/user.js b/chestnut-ui/src/store/modules/user.js index e41dcbdd..beefc1a1 100644 --- a/chestnut-ui/src/store/modules/user.js +++ b/chestnut-ui/src/store/modules/user.js @@ -31,12 +31,8 @@ const user = { actions: { // 登录 Login({ commit }, userInfo) { - const username = userInfo.username.trim() - const password = userInfo.password - const code = userInfo.code - const uuid = userInfo.uuid return new Promise((resolve, reject) => { - login(username, password, code, uuid).then(res => { + login(userInfo).then(res => { setToken(res.data) commit('SET_TOKEN', res.data) resolve() diff --git a/chestnut-ui/src/utils/chestnut.js b/chestnut-ui/src/utils/chestnut.js index 31f16865..c929afd5 100644 --- a/chestnut-ui/src/utils/chestnut.js +++ b/chestnut-ui/src/utils/chestnut.js @@ -365,4 +365,40 @@ export function getResponseCodeErrMsg(code, defaultMsg) { errMsg = i18n.t("Error.Err404") } return errMsg; +} + +function substringAfter(str, findStr) { + if (findStr === "") return ""; + const index = str.indexOf(findStr); + if (index === -1) return ""; + return str.substring(index + findStr.length); +} + +function substringBefore(str, findStr) { + if (findStr === "") return ""; + const index = str.indexOf(findStr); + if (index === -1) return str; + return str.substring(0, index); +} + +export function substringAfterLast(str, findStr) { + if (findStr === '') { + return ''; + } + const index = str.lastIndexOf(findStr); + if (index === -1) { + return ''; + } + return str.substring(index + findStr.length); +} + +export function substringBeforeLast(str, findStr) { + if (findStr === "") return ""; + const index = str.lastIndexOf(findStr); + if (index === -1) return str; + return str.substring(0, index); +} + +export function tableRowNo(pageNo, pageSize, current) { + return (pageNo - 1) * pageSize + current; } \ No newline at end of file diff --git a/chestnut-ui/src/views/cms/ad/adSpaceEditor.vue b/chestnut-ui/src/views/cms/ad/adSpaceEditor.vue index 1cf709cd..d6893199 100644 --- a/chestnut-ui/src/views/cms/ad/adSpaceEditor.vue +++ b/chestnut-ui/src/views/cms/ad/adSpaceEditor.vue @@ -36,152 +36,154 @@ @click="handleGoBack">{{ $t('Common.GoBack') }} - - -
- {{ $t('CMS.Adv.Basic') }} -
-
- - - - - - - - - - - - - - - - + + + +
+ {{ $t('CMS.Adv.AdList') }} +
+ + {{ $t("Common.Select") }} -
-
- - - -
-
-
- -
- {{ $t('CMS.Adv.AdList') }} -
- - - {{ $t("Common.Add") }} - {{ $t('Common.Edit') }} - {{ $t("Common.Delete") }} - - - - {{ $t("Common.Search") }} - {{ $t("Common.Reset") }} - - - - - - - - - - - - - - - - - - - - - - - - - -
+ plain + type="danger" + icon="el-icon-plus" + size="mini" + :disabled="selectedRows.length===0" + @click="handleDeleteAdvertisements">{{ $t("Common.Delete") }} + + + + {{ $t("Common.Search") }} + {{ $t("Common.Reset") }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ $t('CMS.Adv.Basic') }} +
+
+ + + + + + + + + + + + +
+
+ +
+ {{ $t('CMS.Block.PublishPipeProp') }} +
+
+ + + + + +
+
+
+
+
- - - {{ $t("Common.Save") }} - - - {{ $t('CMS.ContentCore.Publish') }} - - - {{ $t('CMS.ContentCore.Preview') }} - - - {{ $t('Common.GoBack') }} - - - - -
- {{ $t('CMS.Block.Basic') }} -
-
- - - - - - - - - - - - - - - - - {{ $t("Common.Select") }} - - - - - -
-
-
- -
- {{ $t('CMS.Block.ManualList') }} -
- - - - - + + + + + + +
+ + + + +
+ {{ $t('CMS.Block.Basic') }} +
+
+ + + + + + + + + + + + +
+
+ +
+ {{ $t('CMS.Block.PublishPipeProp') }} +
+
+ + + + + +
+
+
+
+ @@ -263,6 +264,7 @@ export default { ] }, openTemplateSelector: false, + templatePublishPipeCode: "", title: "", dialogVisible: false, form_item: {}, @@ -324,11 +326,15 @@ export default { } }); }, - handleSelectTemplate () { + handleSelectTemplate (code) { + this.templatePublishPipeCode = code this.openTemplateSelector = true; }, handleTemplateSelected (template) { - this.form.template = template; + let prop = this.form.templates.find(item => item.code == this.templatePublishPipeCode); + if (prop) { + prop.template = template; + } this.openTemplateSelector = false; }, handleTemplateSelectorCancel () { diff --git a/chestnut-ui/src/views/cms/components/ImageGroup/index.vue b/chestnut-ui/src/views/cms/components/ImageGroup/index.vue index 250c5350..4de9960f 100644 --- a/chestnut-ui/src/views/cms/components/ImageGroup/index.vue +++ b/chestnut-ui/src/views/cms/components/ImageGroup/index.vue @@ -28,7 +28,7 @@ v-if="showImageViewer" :on-close="handleImageViewerClose" :initial-index="curIndex" - :url-list="imageSrcList"> + :url-list="originalSrcList"> diff --git a/chestnut-ui/src/views/cms/contentcore/catalogInfo.vue b/chestnut-ui/src/views/cms/contentcore/catalogInfo.vue index 79d8e217..8178c54f 100644 --- a/chestnut-ui/src/views/cms/contentcore/catalogInfo.vue +++ b/chestnut-ui/src/views/cms/contentcore/catalogInfo.vue @@ -609,7 +609,7 @@ export default { publishPipePropKeys: [ propKey ] } catalogApi.applyPublishPipeToChildren(data).then(res => { - this.$modal.msgSuccess(res.msg); + this.$modal.msgSuccess(this.$t('Common.OpSuccess')); }); }, handleLinkTo(type) { @@ -661,12 +661,11 @@ export default { }, handleSortCatalog() { if (this.sortValue == 0) { - this.$modal.msgWarning("排序值不能为0"); return; } let data = { catalogId: this.catalogId, sort: this.sortValue } catalogApi.sortCatalog(data).then(response => { - this.$modal.msgSuccess(response.msg); + this.$modal.msgSuccess(this.$t('Common.OpSuccess')); this.showSortPop = false; this.sortValue = 0; this.$emit("reload"); diff --git a/chestnut-ui/src/views/cms/contentcore/contentEditor.vue b/chestnut-ui/src/views/cms/contentcore/contentEditor.vue index ab41f2f6..59b06dd4 100644 --- a/chestnut-ui/src/views/cms/contentcore/contentEditor.vue +++ b/chestnut-ui/src/views/cms/contentcore/contentEditor.vue @@ -307,7 +307,7 @@ - +
+ \ No newline at end of file diff --git a/chestnut-ui/src/views/cms/imageAlbum/editor.vue b/chestnut-ui/src/views/cms/imageAlbum/editor.vue index 080e7ec6..f7460be2 100644 --- a/chestnut-ui/src/views/cms/imageAlbum/editor.vue +++ b/chestnut-ui/src/views/cms/imageAlbum/editor.vue @@ -63,7 +63,7 @@ :open.sync="openResourceDialog" rtype="image" :upload-limit="uploadLimit" - :single="uploadLimit==1" + :single="singleUpload" @ok="handleResourceDialogOk"> +
+ + + + +
+ + + \ No newline at end of file diff --git a/chestnut-ui/src/views/components/captcha/text/index.vue b/chestnut-ui/src/views/components/captcha/text/index.vue new file mode 100644 index 00000000..a82fdc6d --- /dev/null +++ b/chestnut-ui/src/views/components/captcha/text/index.vue @@ -0,0 +1,89 @@ + + + \ No newline at end of file diff --git a/chestnut-ui/src/views/error/401.vue b/chestnut-ui/src/views/error/401.vue index 43ad8b79..bfe8ad72 100644 --- a/chestnut-ui/src/views/error/401.vue +++ b/chestnut-ui/src/views/error/401.vue @@ -1,22 +1,14 @@ + + diff --git a/chestnut-ui/src/views/error/404.vue b/chestnut-ui/src/views/error/404.vue index 563a6e0b..41964698 100644 --- a/chestnut-ui/src/views/error/404.vue +++ b/chestnut-ui/src/views/error/404.vue @@ -2,23 +2,22 @@
- 404 - 404 - 404 - 404 + 404 + 404 + 404 + 404
- {{ Error.Err404 }} + {{ $t('Error.PageNotFound') }}
- {{ message }}
- {{ Error.Err404Tip }} + {{ $t('Error.Err404Tip') }}
- {{ Error.GoHome }} + {{ $t('Error.GoHome') }}
@@ -28,12 +27,7 @@ diff --git a/chestnut-ui/src/views/index.vue b/chestnut-ui/src/views/index.vue index a78cf689..aa0f0b3c 100644 --- a/chestnut-ui/src/views/index.vue +++ b/chestnut-ui/src/views/index.vue @@ -5,6 +5,11 @@ + + + + + @@ -24,6 +29,7 @@ import SysShortcut from '@/views/system/dashboard/shortcut' import ServerInfo from '@/views/system/dashboard/serverInfo' import CmsSiteVisitStat from '@/views/cms/dashboard/siteVisitStat' import CmsSiteDataStat from '@/views/cms/dashboard/siteDataStat' +import CmsSitePanel from '@/views/cms/dashboard/sitePanel' export default { name: 'Index', @@ -33,6 +39,7 @@ export default { 'server-info': ServerInfo, 'cms-site-visit-stat': CmsSiteVisitStat, 'cms-site-data-stat': CmsSiteDataStat, + 'cms-site-panel': CmsSitePanel, }, data() { return { diff --git a/chestnut-ui/src/views/login.vue b/chestnut-ui/src/views/login.vue index 1a33700d..bd61628f 100644 --- a/chestnut-ui/src/views/login.vue +++ b/chestnut-ui/src/views/login.vue @@ -23,19 +23,9 @@ - - - - - + + + {{ $t('Login.RememberMe') }} @@ -65,26 +55,27 @@ - \ No newline at end of file diff --git a/chestnut-ui/src/views/system/user/index.vue b/chestnut-ui/src/views/system/user/index.vue index 12937ef7..c72fad72 100644 --- a/chestnut-ui/src/views/system/user/index.vue +++ b/chestnut-ui/src/views/system/user/index.vue @@ -201,6 +201,9 @@ + + + diff --git a/chestnut-ui/src/views/system/user/profile/userInfo.vue b/chestnut-ui/src/views/system/user/profile/userInfo.vue index d39c78e8..b59386e0 100644 --- a/chestnut-ui/src/views/system/user/profile/userInfo.vue +++ b/chestnut-ui/src/views/system/user/profile/userInfo.vue @@ -3,6 +3,9 @@ + + + diff --git a/pom.xml b/pom.xml index 013c8a79..61e62117 100644 --- a/pom.xml +++ b/pom.xml @@ -4,49 +4,47 @@ com.chestnut chestnut - 1.5.5 + 1.5.6 pom ChestnutCMS 栗子内容管理系统 - 1.5.5 + 1.5.6 UTF-8 UTF-8 17 - 3.1.1 - 1.18.26 - 8.2.0 - 3.1.7 - 3.1.7 - 3.5.5 - 4.3.0 - 3.25.2 - 2.15.1 - 1.13.0 + 1.18.38 + 8.4.0 + 3.4.5 + 3.4.5 + 3.5.12 + 4.3.1 + 3.46.0 + 2.19.0 + 1.13.1 2.3.2 1.21 - 6.4.10 - 2.3 + 6.8.1 + 2.4.1 1.0.6 1.11.9 - 3.17.1 - 5.6.166 - 8.5.5 + 3.18.2 + 5.6.245 + 8.5.17 2.7.0 - 3.3.3 - 1.37.0 - 1.17.2 + 1.2.0 + 1.43.0 + 1.20.1 1.4.0 - 0.8.0 - 3.4.0 + 0.9.0 + 3.5.0 3.3.0 - 6.0.0 + 6.1.0 3.0.5 - 2.4.0 - 9.22.3 - 1.0.2 + 3.1.0 + 5.2.0 @@ -170,6 +168,11 @@ mybatis-plus-spring-boot3-starter ${mybatis-plus.version} + + com.baomidou + mybatis-plus-jsqlparser + ${mybatis-plus.version} + @@ -259,11 +262,11 @@ ${ip2region.version} - + - com.alibaba - easyexcel - ${easyexcel.version} + cn.idev.excel + fastexcel + ${fastexcel.version} @@ -348,6 +351,13 @@ ${chestnut.version} + + + com.chestnut + chestnut-common-captcha + ${chestnut.version} + + com.chestnut @@ -355,6 +365,13 @@ ${chestnut.version} + + + com.chestnut + chestnut-captcha + ${chestnut.version} + + com.chestnut @@ -502,11 +519,6 @@ chestnut-cms-stat ${chestnut.version} - - com.chestnut - chestnut-cms-stat-pro - ${chestnut.version} -