diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000000000000000000000000000000000000..8b0f7d9667c2dceb33928f5342c6ff98d2f9480c --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,42 @@ +{ + "permissions": { + "allow": [ + "Bash(mysql:*)", + "Bash(mvn clean compile:*)", + "Bash(gradle --version:*)", + "Bash(whereis:*)", + "Bash(where:*)", + "Bash(findstr:*)", + "Bash(java:*)", + "Bash(git checkout:*)", + "Bash(git restore:*)", + "Bash(find:*)", + "Bash(cat:*)", + "Bash(dir:*)", + "Read(//d/**)", + "Bash(mvn compile:*)", + "Bash(mvn spring-boot:run:*)", + "Read(//c/Users/29285/.m2/repository/**)", + "Bash(cd:*)", + "Bash(npm run dev:*)", + "Bash(sed:*)", + "Bash(grep:*)", + "Bash(awk:*)", + "Bash(del:*)", + "WebSearch", + "Bash(curl:*)", + "Bash(npm run build:*)", + "Bash(npm run:*)", + "Read(//d//**)", + "Bash(gradle clean build:*)", + "Bash(git reset:*)", + "Bash(git revert:*)", + "Bash(git commit:*)", + "Bash(git merge-base:*)", + "Bash(git cherry-pick:*)", + "Bash(git add:*)" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000000000000000000000000000000000..d6afff18d61c28220722c50ccd7dae1715645844 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,314 @@ +# River13河道管理系统项目记忆 + + + +  ## 项目基本信息 + +  - 项目名称:River13河道管理系统 + +  - 项目框架:Ruoyi框架 + +  - 主要功能:文章管理、审核管理、部门权限控制 + +  - 主要修改模块:uncheck模块(未审核文章管理) + + + +  ## 技术栈 + +  - 后端:Java、Spring Boot、MyBatis + +  - 前端:Vue.js、Element UI + +  - 数据库:MySQL + +  - 文件存储:阿里云OSS + + + +  ## 权限控制逻辑 + +  - admin用户:超级管理员,可查看所有数据 + +  - 市局/总局用户:可查看所有分局数据,可进行条件查询 + +  - 分局用户:只能查看自己分局的数据,通过articleOrigin字段过滤 + +  - 分局用户在list和list2接口中都受到部门权限限制 + +  - 分局用户在list接口中还受到栏目权限限制 + + + +  ## 关键文件路径 + +  - 后端Controller:ruoyi-admin/src/main/java/com/ruoyi/controller/ArticleUncheckController.java + +  - 后端Service:ruoyi-admin/src/main/java/com/ruoyi/service/impl/ArticleUncheckServiceImpl.java + +  - 后端Mapper:ruoyi-admin/src/main/resources/mapper/uncheck/ArticleUncheckMapper.xml + +  - 前端页面:ruoyi-ui/src/views/uncheck/uncheck/index.vue + +  - 前端API:ruoyi-ui/src/api/uncheck/uncheck.js + + + +  ## 已完成的修改 + +  1. 修复了分局用户在list和list2接口中的权限控制逻辑 + +  2. 确保分局用户只能查看自己所属分局的数据 + +  3. 修复了Mapper中的SQL语法错误 + +  4. 移除了分局用户前端界面中的文章来源查询功能 + +  5. 实现了getDeptArticleOrigin方法来正确获取分局名称 + + + +# 项目历史对话和视频功能实现记录 + +## 项目概述 + +本项目是一个基于Ruoyi框架的视频管理系统,主要实现了视频排序功能。 + +## 已实现功能 + +1. 视频按类型独立排序 +2. 视频上下移动操作 +3. 视频置顶操作 +4. 跨分页排序支持 +5. 后台列表按排序值显示 + +## 核心实现细节 + +### 数据库设计 + +- 添加了sort_order字段用于视频排序 +- 每个视频类型维护独立的排序序列 + +### 后端实现 + +- Controller层提供了moveUp、moveDown、moveToTop等接口 +- Service层实现了具体的排序逻辑 +- Mapper层包含了相关的SQL查询语句 + +### 前端实现 + +- 添加了置顶、上移、下移操作按钮 +- 实现了表格按排序值显示 +- 提供了相应的API调用逻辑 + +## 重要文件位置 + +- 视频排序开发文档: C:\Users\29285\Desktop\视频排序功能开发文档.md + +## 历史问题记录 + +1. 修复了前端API函数未导入的问题 +2. 解决了后端Mapper参数名称不匹配的问题 +3. 修正了SQL语法错误 +4. 修复了int类型比较的错误 + +## 已完成任务 + +- 实现了视频排序功能的所有核心逻辑 +- 生成了详细的开发文档 +- 解决了所有已知的bug和问题 + +# 草稿箱功能实现记录 + +## 功能需求 +1. 已审核那里草稿箱,不用做权限控制,反正只有市局能看到这个导航栏进行操作 +2. 新增可以将文章先放入到草稿箱,此时这个文章属于没有发布的状态,所以列表就不能显示这篇未发布处于草稿箱的文章(能不能建个单独的草稿箱,逻辑回更好一点,草稿箱里面点击发布就可以降草稿箱里面的文章发布到article中了,修改也挺方便的。) +3. 如果是修改已存在的文章,那么如果我没有修改完,我可以存放在草稿箱里,我在草稿箱继续修改完后,可以发布,就更新这条对应的数据。 +4. 草稿箱最好搞个可以删除草稿的功能 +5. 新增修改没有上传审批表要提醒哦 + +## 技术实现 + +### 后端实现 +1. 在Article实体类中添加draft字段(0-已发布,1-草稿) +2. 在ArticleController中添加四个草稿相关接口: + - saveDraft:保存草稿 + - updateDraft:更新草稿 + - publishDraft:发布草稿 + - listDraft:查询草稿列表 +3. 在ArticleService中添加selectDraftList方法声明 +4. 在ArticleServiceImpl中实现selectDraftList方法 +5. 在ArticleMapper中添加selectDraftList方法声明 +6. 在ArticleMapper.xml中添加selectDraftList的SQL查询语句 + +### 前端实现 +1. 在index.vue中添加草稿箱图标按钮 +2. 添加草稿箱弹窗对话框 +3. 添加草稿相关数据变量和方法: + - draftBoxVisible:控制草稿箱对话框显示 + - draftList:草稿列表数据 + - draftLoading:草稿列表加载状态 + - handleOpenDraftBox:打开草稿箱方法 + - loadDraftList:加载草稿列表方法 + - handleEditDraft:编辑草稿方法 + - handlePublishDraft:发布草稿方法 + - handleDeleteDraft:删除草稿方法 +4. 修改submitForm方法,添加审批表验证逻辑,确保在新增和修改时未上传审批表会提醒用户 + +### 核心代码变更 + +#### Article.java(实体类) +```java +/** 草稿状态(0-已发布 1-草稿) */ +@Excel(name = "草稿状态", readConverterExp = "0=已发布,1=草稿") +@TableField(value = "draft") +private Integer draft; +``` + +#### ArticleController.java(控制器) +```java +/** + * 保存草稿 + */ +@PreAuthorize("@ss.hasPermi('article:article:add')") +@Log(title = "article", businessType = BusinessType.INSERT) +@PostMapping("/draft") +public AjaxResult saveDraft(@RequestBody Article article) { + if (Objects.isNull(article)) { + return error("请将数据填充完整"); + } + + // 设置为草稿状态 + article.setDraft(1); + + LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); + SysUser sysUser = loginUser.getUser(); + String userName = sysUser.getUserName(); + + // 获取当前日期并格式化为字符串 + LocalDate now = LocalDate.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + String currentDate = now.format(formatter); + article.setPubdate(currentDate); + + // 获取用户ip地址 + String ip = sysUser.getLoginIp(); + article.setIp(ip); + + // 使用MyBatis-Plus直接保存 + return toAjax(articleService.save(article) ? 1 : 0); +} + +/** + * 更新草稿 + */ +@PreAuthorize("@ss.hasPermi('article:article:edit')") +@Log(title = "article", businessType = BusinessType.UPDATE) +@PutMapping("/draft") +public AjaxResult updateDraft(@RequestBody Article article) { + // 设置为草稿状态 + article.setDraft(1); + + LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); + SysUser sysUser = loginUser.getUser(); + String ip = sysUser.getLoginIp(); + article.setIp(ip); + + // 使用MyBatis-Plus直接更新 + return toAjax(articleService.updateById(article) ? 1 : 0); +} + +/** + * 发布草稿 + */ +@PreAuthorize("@ss.hasPermi('article:article:edit')") +@Log(title = "article", businessType = BusinessType.UPDATE) +@PutMapping("/publish/{articleId}") +public AjaxResult publishDraft(@PathVariable Long articleId) { + Article article = articleService.selectArticleByArticleId(articleId); + if (article == null) { + return error("文章不存在"); + } + + // 设置为已发布状态 + article.setDraft(0); + + // 使用MyBatis-Plus直接更新 + return toAjax(articleService.updateById(article) ? 1 : 0); +} + +/** + * 查询草稿列表 + * 草稿箱不用做权限控制,只有市局能看到 + */ +@GetMapping("/draft/list") +public TableDataInfo listDraft(Article article) { + // 设置查询条件为草稿状态 + article.setDraft(1); + + LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); + SysUser sysUser = loginUser.getUser(); + String userName = sysUser.getUserName(); + String deptName = sysUser.getDept() != null ? sysUser.getDept().getDeptName() : null; + + // 只有admin用户或市局/总局用户才能访问草稿箱 + boolean canAccessDraft = "admin".equals(userName) || (deptName != null && "市局/总局".equals(deptName)); + + if (!canAccessDraft) { + // 如果不是市局用户,返回空列表 + return getDataTable(new ArrayList<>()); + } + + startPage(); + // 草稿箱不需要权限控制,直接查询所有草稿 + List
list = articleService.selectArticleList(article); + return getDataTable(list); +} +``` + +#### ArticleMapper.xml(Mapper XML文件) +```xml + +``` + +#### index.vue(前端页面) +```javascript +// 在提交表单方法中添加审批表验证 +async submitForm() { + this.$refs["form"].validate(async valid => { + if (!valid) return; + + // 检查是否上传了审批表 + if (!this.form.appval || this.form.appval === '') { + this.$modal.msgError("请上传审批表"); + return; + } + + // ... 其余代码保持不变 + }); +} +``` + +### 功能测试要点 +1. 确保草稿箱不进行权限控制,仅市局可见 +2. 完善草稿箱功能,支持新增和修改文章的草稿保存 +3. 实现草稿箱中的文章发布功能,发布后更新到主文章列表 +4. 添加草稿箱中的草稿删除功能 +5. 确保新增修改时未上传审批表会提醒用户 + +所有功能均已实现并测试通过。 \ No newline at end of file diff --git a/add_attachment_to_article.sql b/add_attachment_to_article.sql new file mode 100644 index 0000000000000000000000000000000000000000..4706baf7fb0aa29f0725ac735403a59919f91733 --- /dev/null +++ b/add_attachment_to_article.sql @@ -0,0 +1,2 @@ +-- 为article表添加附件字段 +ALTER TABLE article ADD COLUMN attachment VARCHAR(255) COMMENT '附件路径'; \ No newline at end of file diff --git a/add_attachment_to_draft_article.sql b/add_attachment_to_draft_article.sql new file mode 100644 index 0000000000000000000000000000000000000000..05427a5709dd3143d2df2b9756e87728bc7711b9 --- /dev/null +++ b/add_attachment_to_draft_article.sql @@ -0,0 +1,2 @@ +-- 为draft_article表添加附件字段 +ALTER TABLE draft_article ADD COLUMN attachment VARCHAR(255) COMMENT '附件路径'; \ No newline at end of file diff --git a/add_attachment_to_uncheck_draft.sql b/add_attachment_to_uncheck_draft.sql new file mode 100644 index 0000000000000000000000000000000000000000..772b606096661eda5771663d1f54f52bae33aa37 --- /dev/null +++ b/add_attachment_to_uncheck_draft.sql @@ -0,0 +1,2 @@ +-- 为uncheck_draft表添加附件字段 +ALTER TABLE uncheck_draft ADD COLUMN attachment VARCHAR(255) COMMENT '附件路径'; \ No newline at end of file diff --git a/add_pdf_image_paths_field.sql b/add_pdf_image_paths_field.sql new file mode 100644 index 0000000000000000000000000000000000000000..19b983ac07269252a244011286c641cfcda5adf5 --- /dev/null +++ b/add_pdf_image_paths_field.sql @@ -0,0 +1,5 @@ +-- 检查并添加pdf_image_paths字段到article表 +ALTER TABLE article ADD COLUMN IF NOT EXISTS pdf_image_paths TEXT COMMENT 'PDF图片路径列表(逗号分隔)'; + +-- 如果上面的语句不支持,可以使用以下语句: +-- ALTER TABLE article ADD COLUMN pdf_image_paths TEXT COMMENT 'PDF图片路径列表(逗号分隔)'; \ No newline at end of file diff --git a/add_pdf_image_paths_to_article.sql b/add_pdf_image_paths_to_article.sql new file mode 100644 index 0000000000000000000000000000000000000000..5c2b8eef382208b57bc12174b9f67823f3e9b6d2 --- /dev/null +++ b/add_pdf_image_paths_to_article.sql @@ -0,0 +1,2 @@ +-- 添加pdf_image_paths字段到article表 +ALTER TABLE article ADD COLUMN pdf_image_paths TEXT COMMENT 'PDF图片路径列表(逗号分隔)'; \ No newline at end of file diff --git a/add_pubdate_to_river_news.sql b/add_pubdate_to_river_news.sql new file mode 100644 index 0000000000000000000000000000000000000000..66343fe61dd32134868dfa3300a82c7b7c108eb5 --- /dev/null +++ b/add_pubdate_to_river_news.sql @@ -0,0 +1 @@ +ALTER TABLE river_news ADD COLUMN pubdate VARCHAR(255) NULL COMMENT '发布日期'; diff --git a/add_sort_order_to_partyclass.sql b/add_sort_order_to_partyclass.sql new file mode 100644 index 0000000000000000000000000000000000000000..2c8ea79b6c3199693387b2c91463e4cfe18b1c89 --- /dev/null +++ b/add_sort_order_to_partyclass.sql @@ -0,0 +1,39 @@ +-- 为partyclass表添加排序字段 +ALTER TABLE partyclass ADD COLUMN sort_order INT DEFAULT 0 COMMENT '排序字段'; + +-- 为现有数据按类型分别设置排序值 +-- 使用存储过程为每个类型动态设置排序值 +DROP PROCEDURE IF EXISTS set_sort_order_by_type; + +DELIMITER $$ +CREATE PROCEDURE set_sort_order_by_type() +BEGIN + DECLARE done INT DEFAULT FALSE; + DECLARE current_type INT; + DECLARE type_cursor CURSOR FOR SELECT DISTINCT type FROM partyclass WHERE type IS NOT NULL; + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; + + OPEN type_cursor; + + type_loop: LOOP + FETCH type_cursor INTO current_type; + IF done THEN + LEAVE type_loop; + END IF; + + SET @row_number = 0; + UPDATE partyclass + SET sort_order = (@row_number := @row_number + 1) + WHERE type = current_type + ORDER BY id; + END LOOP; + + CLOSE type_cursor; +END$$ +DELIMITER ; + +-- 执行存储过程 +CALL set_sort_order_by_type(); + +-- 删除存储过程 +DROP PROCEDURE set_sort_order_by_type; \ No newline at end of file diff --git a/attachment_field_fix_documentation.md b/attachment_field_fix_documentation.md new file mode 100644 index 0000000000000000000000000000000000000000..1293029b85792bcbc554338eb59539fba932b5e6 --- /dev/null +++ b/attachment_field_fix_documentation.md @@ -0,0 +1,23 @@ +# 附件字段默认值问题修复说明 + +## 问题描述 +在操作草稿文章时,出现"Field 'attachment' doesn't have a default value"错误。 + +## 问题原因 +1. `draft_article`表和`uncheck_draft`表在创建时没有定义`attachment`字段 +2. 后续通过ALTER TABLE语句添加了`attachment`字段,但没有设置默认值 +3. MyBatis的Mapper文件中,当attachment字段为null时不会将其包含在INSERT语句中 +4. 当数据库字段没有默认值且不在INSERT语句中时,就会出现该错误 + +## 解决方案 +1. 创建SQL脚本为`attachment`字段添加默认值: + - `update_draft_article_attachment_default.sql` + - `update_uncheck_draft_attachment_default.sql` + +2. 修改MyBatis的Mapper XML文件,确保在INSERT语句中始终包含`attachment`字段: + - `DraftArticleMapper.xml` + - `UncheckDraftMapper.xml` + +## 实施步骤 +1. 执行SQL脚本更新数据库表结构 +2. 部署更新后的Mapper XML文件 \ No newline at end of file diff --git a/migrate_river_view_to_partyclass.sql b/migrate_river_view_to_partyclass.sql new file mode 100644 index 0000000000000000000000000000000000000000..3ab43bb91e8c0e544c4a488a3b22395744ee9259 --- /dev/null +++ b/migrate_river_view_to_partyclass.sql @@ -0,0 +1,17 @@ +-- 将river_view数据迁移到partyclass表 +INSERT INTO partyclass (name, file, cover_img, x, y, pubdate, type) +SELECT + name, + video as file, + cover_img, + x, + y, + CURDATE() as pubdate, -- 设置默认日期为当前日期 + 2 as type -- 设置视频类型为2 +FROM river_view; + +-- 验证迁移结果 +SELECT COUNT(*) as migrated_count FROM partyclass WHERE file IS NOT NULL AND file != ''; + +-- 如果迁移成功,可以选择删除river_view表(可选) +-- DROP TABLE river_view; \ No newline at end of file diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index ad96aa46bb14ff5feb4a6803b2f082c0d38a3c55..eb3f01a18c42fc06c5f4f2caadf1dd865ac6cb11 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -96,6 +96,7 @@ pdfbox 2.0.29 + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java index a9e3b578bb73ebda41171865467595c6a24c20cf..b7fd86b12bc6b60d2fb5ce3fde98212b9081ec06 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java @@ -1,10 +1,14 @@ package com.ruoyi; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.dromara.x.file.storage.spring.EnableFileStorage; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.transaction.annotation.EnableTransactionManagement; @@ -38,4 +42,11 @@ public class RuoYiApplication " | | \\ / \\ / \n" + " ''-' `'-' `-..-' "); } + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/config/AsyncConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/config/AsyncConfig.java index bb921f9c9a4a6e98bbb939b908f3a3a7e7c47752..eb06fd156a189521bb536dda488a547ac5b793d6 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/config/AsyncConfig.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/config/AsyncConfig.java @@ -6,6 +6,7 @@ import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; @Configuration @EnableAsync @@ -14,10 +15,31 @@ public class AsyncConfig { @Bean("pdfParseTaskExecutor") public Executor pdfParseTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(2); - executor.setMaxPoolSize(5); + executor.setCorePoolSize(8); + executor.setMaxPoolSize(16); executor.setQueueCapacity(100); + executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix("pdf-parse-task-"); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } + @Bean(name = "uploadTaskExecutor") + public Executor uploadTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + // 核心线程数:根据CPU核心数和业务量调整(建议CPU核心数 * 2) + executor.setCorePoolSize(8); + // 最大线程数:处理峰值流量 + executor.setMaxPoolSize(16); + // 队列容量:缓冲等待任务(避免瞬间任务量过大导致拒绝) + executor.setQueueCapacity(100); + // 空闲线程存活时间:60秒 + executor.setKeepAliveSeconds(60); + // 线程名前缀:便于日志追踪 + executor.setThreadNamePrefix("FileUpload-"); + // 任务拒绝策略:队列满时由提交线程处理(避免任务丢失) + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + // 初始化线程池 executor.initialize(); return executor; } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/AdministrationController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/AdministrationController.java index a0a999eeab2fc55aa97111fdbf181b1efa31c006..6becfe081343b647854874211d9ecffe282f03a2 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/AdministrationController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/AdministrationController.java @@ -1,10 +1,14 @@ package com.ruoyi.controller; +import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.domain.Administration; import com.ruoyi.domain.AdsRecoverStation; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.service.IAdministrationService; import com.ruoyi.common.annotation.Anonymous; @@ -53,135 +57,69 @@ public class AdministrationController extends BaseController @Autowired private SftpProperties sftpProperties; - - - - /** - * 将文件存储到oss和本地 - */ - @Anonymous - @PutMapping("/filePath/{id}") - public AjaxResult uploadFile(@PathVariable Long id,@RequestParam MultipartFile file) throws Exception { - try { - // 检查文件是否为空 - if (file.isEmpty()) { - return AjaxResult.error("上传文件为空"); - } - - // 获取 base-path - String basePath = "administrationFilePath/"; // 直接在这里设置基础路径,或者从配置文件中读取 - - // 指定oss保存文件路径 - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + file.getOriginalFilename(); - // 上传图片,成功返回文件信息 - FileInfo fileInfo = fileStorageService.of(file).setPath(objectName).upload(); - - // 设置返回结果 - AjaxResult ajax = AjaxResult.success(); - ajax.put("url", fileInfo.getUrl()); - ajax.put("fileName", fileInfo.getUrl()); // 注意:这里的值要改为url,前端访问的地址,需要文件的地址 而不是文件名称 - ajax.put("newFileName", fileInfo.getUrl()); - ajax.put("originalFilename", file.getOriginalFilename()); - - // 创建Administration对象并设置ID和filePath - Administration administration = new Administration(); - administration.setId(id); // 你需要实现一个方法来生成唯一的ID,或者从其他地方获取ID - administration.setFilePath(fileInfo.getUrl()); - - // 更新数据库中的filePath字段 - boolean result = administrationService.updateById(administration); - if (!result) { - return AjaxResult.error("更新文件路径失败"); - } - - return ajax; - - } catch (Exception e) { - return AjaxResult.error(e.getMessage()); - } - } - + @Autowired + private ServerConfig serverConfig; /** * 更新审批管理文件路径,保存在远程服务器 */ @Anonymous - @PutMapping("/updateRemoteFilepath/{id}") + @PutMapping("/filePath/{id}") public AjaxResult remote(@PathVariable Long id, @RequestParam MultipartFile file) { // 检查文件是否为空 if (file.isEmpty()) { return AjaxResult.error("上传文件为空"); } - try { - // 使用配置文件中的参数上传到远程服务器 - String fileUrl = FileUploadUtil.uploadFileToRemoteServer( - sftpProperties.toSftpConfig(), - file, - "administrationFilePath" - ); - - // 设置返回结果 - AjaxResult ajax = AjaxResult.success(); - ajax.put("url", fileUrl); - - Administration administration = new Administration(); - administration.setId(id); - administration.setFilePath(fileUrl); - - boolean result = administrationService.updateById(administration); - if (!result) { - return AjaxResult.error("更新失败"); + int maxRetries = 3; // 最大重试次数 + int retryCount = 0; + + while (retryCount <= maxRetries) { + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url = url.replaceFirst("http://", "https://"); + // 设置返回结果 + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + + Administration administration = new Administration(); + administration.setId(id); + administration.setFilePath(url); + + boolean result = administrationService.updateById(administration); + if (!result) { + return AjaxResult.error("更新失败"); + } + return ajax; + + } catch (Exception e) { + retryCount++; + if (retryCount > maxRetries) { + e.printStackTrace(); + return AjaxResult.error("文件上传失败: " + e.getMessage()); + } + + // 等待一段时间后重试 + try { + Thread.sleep(1000); // 递增等待时间 + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + return AjaxResult.error("上传被中断"); + } } - return ajax; - - } catch (Exception e) { - e.printStackTrace(); - return AjaxResult.error("文件上传失败: " + e.getMessage()); } - } - - - - /** - * 更新审批管理文件路径,保存在本地 - */ - @Anonymous - @PutMapping("/updateFilepath/{id}") - public AjaxResult updateFilepath(@PathVariable Long id, @RequestParam MultipartFile file) { - - // 检查文件是否为空 - if (file.isEmpty()) { - return AjaxResult.error("上传文件为空"); - } - // 获取 base-path - String basePath = "administrationFilePath/"; // 直接在这里设置基础路径,或者从配置文件中读取 - - // 指定oss保存文件路径 - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + file.getOriginalFilename(); - // 上传图片,成功返回文件信息 - FileInfo fileInfo = fileStorageService.of(file).setPath(objectName).upload(); - - // 设置返回结果 - AjaxResult ajax = AjaxResult.success(); - ajax.put("url", fileInfo.getUrl()); - ajax.put("fileName", fileInfo.getUrl()); // 注意:这里的值要改为url,前端访问的地址,需要文件的地址 而不是文件名称 - ajax.put("newFileName", fileInfo.getUrl()); - ajax.put("originalFilename", file.getOriginalFilename()); - Administration administration = new Administration(); - administration.setId(id); - administration.setFilePath(fileInfo.getUrl()); - - boolean result = administrationService.updateById(administration); - if (!result) { - return AjaxResult.error("更新失败"); - } - return ajax; + return AjaxResult.error("文件上传失败"); } + /** * 查询审批种类 */ + @Anonymous @GetMapping("/kind") public TableDataInfo selectKindList() { // 1. 启用分页(自动读取前端的 pageNum 和 pageSize) @@ -267,6 +205,7 @@ public class AdministrationController extends BaseController */ // @PreAuthorize("@ss.hasPermi('administration:administration:query')") @GetMapping(value = "/kindkind") + @Anonymous public TableDataInfo getInfoByKind(@RequestParam(required = false) String kind) { // 1. 启用分页(自动读取前端的 pageNum 和 pageSize) startPage(); @@ -322,37 +261,45 @@ public class AdministrationController extends BaseController administration1.setClick(Long.valueOf(administration.get("click"))); if (administration.get("date") != null) administration1.setDate(administration.get("date")); - try { - if (file != null && !file.isEmpty()){ - // 获取 base-path - String basePath = "administrationFilePath/"; // 直接在这里设置基础路径,或者从配置文件中读取 - - // 指定oss保存文件路径 - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + file.getOriginalFilename(); - // 上传图片,成功返回文件信息 - FileInfo fileInfo = fileStorageService.of(file).setPath(objectName).upload(); - - // 设置返回结果 - AjaxResult ajax = AjaxResult.success(); - ajax.put("url", fileInfo.getUrl()); - ajax.put("fileName", fileInfo.getUrl()); // 注意:这里的值要改为url,前端访问的地址,需要文件的地址 而不是文件名称 - ajax.put("newFileName", fileInfo.getUrl()); - ajax.put("originalFilename", file.getOriginalFilename()); - - //filePath - administration1.setFilePath(fileInfo.getUrl()); + int maxRetries = 3; // 最大重试次数 + int retryCount = 0; + String url = null; + + while (retryCount <= maxRetries) { + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + url = serverConfig.getUrl() + fileName; + url = url.replaceFirst("http://", "https://"); + break; // 成功则退出重试循环 + } catch (Exception e) { + retryCount++; + if (retryCount > maxRetries) { + throw e; // 达到最大重试次数,抛出异常 + } + // 等待一段时间后重试 + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException("上传被中断", ie); + } + } } - else - administration1.setFilePath(null); - - + // 设置返回结果 + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + administration1.setFilePath(url); + return toAjax(administrationService.insertAdministration(administration1)); } catch (Exception e) { - return AjaxResult.error(e.getMessage()); + e.printStackTrace(); + return AjaxResult.error("文件上传失败: " + e.getMessage()); } - return toAjax(administrationService.insertAdministration(administration1)); - } + } /** * 修改审批管理 */ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/AdsRecoverStationController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/AdsRecoverStationController.java index d430d84642a73a57be1f251f9d2434b4944c467c..b322b64bed12df9ae9db5f363c533af1c34e121a 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/AdsRecoverStationController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/AdsRecoverStationController.java @@ -3,6 +3,7 @@ package com.ruoyi.controller; import java.util.List; import javax.servlet.http.HttpServletResponse; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.mapper.AdsRecoverStationMapper; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; @@ -36,6 +37,8 @@ public class AdsRecoverStationController extends BaseController @Autowired private IAdsRecoverStationService adsRecoverStationService; + @Autowired + private ServerConfig serverConfig; /** * 恢复数据到文章表 diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/AiController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/AiController.java index 0b46b563d7a9187d7e4a61c3a86ac31d7911fb81..1a45831bbb163b03f3d9361be41ed402aed75406 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/AiController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/AiController.java @@ -7,6 +7,12 @@ import java.util.List; import javax.servlet.http.HttpServletResponse; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.framework.config.ServerConfig; +import com.ruoyi.util.FileUploadUtil; +import com.ruoyi.util.SftpProperties; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.springframework.format.annotation.DateTimeFormat; @@ -41,6 +47,10 @@ public class AiController extends BaseController @Autowired private FileStorageService fileStorageService;//注入实列 + @Autowired + private SftpProperties sftpProperties; + @Autowired + private ServerConfig serverConfig; /** * 查询ai列表 */ @@ -97,16 +107,19 @@ public class AiController extends BaseController if (title != null) ai.setTitle(title); if (date != null) ai.setDate(date); - // 处理文件上传 + // 处理文件上传 - 改为远程服务器上传方式 if (file != null && !file.isEmpty()) { - String basePath = "aiFiles/"; - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) - + "/" + file.getOriginalFilename(); - - FileInfo fileInfo = fileStorageService.of(file) - .setPath(objectName) - .upload(); - ai.setFile(fileInfo.getUrl()); + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url = url.replaceFirst("http://", "https://"); + ai.setFile(url); + } catch (Exception e) { + return AjaxResult.error("文件上传失败: " + e.getMessage()); + } } // 至少需要有一个有效字段 @@ -121,8 +134,6 @@ public class AiController extends BaseController } } - - /** * 修改ai */ @@ -151,19 +162,22 @@ public class AiController extends BaseController if (title != null) updateAi.setTitle(title); if (date != null) updateAi.setDate(date); - // 处理文件更新 + // 处理文件更新 - 改为远程服务器上传方式 if (file != null && !file.isEmpty()) { - String basePath = "aiFiles/"; - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) - + "/" + file.getOriginalFilename(); - - FileInfo fileInfo = fileStorageService.of(file) - .setPath(objectName) - .upload(); - updateAi.setFile(fileInfo.getUrl()); + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url = url.replaceFirst("http://", "https://"); + updateAi.setFile(url); + + } catch (Exception e) { + return AjaxResult.error("文件上传失败: " + e.getMessage()); + } } - // 执行部分更新 return toAjax(aiService.updateAi(updateAi)); @@ -172,6 +186,7 @@ public class AiController extends BaseController } } + @Anonymous @Log(title = "ai", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/ArticleController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/ArticleController.java index 46e5cd1060422cd506288eb4ae8f2e0295ad6759..a24649983c7d2daf7e87393572c20408a2abd412 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/ArticleController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/ArticleController.java @@ -7,31 +7,41 @@ import java.net.URL; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.stream.Collectors; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletResponse; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.common.utils.uuid.UUID; -import com.ruoyi.domain.RecoverStation; +import com.ruoyi.domain.*; import com.ruoyi.domain.vo.ArticleColumnVo; import com.ruoyi.domain.vo.ArticleOriginDateVo; import com.ruoyi.domain.vo.DeptArticleCountVo; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.mapper.ArticleMapper; import com.ruoyi.mapper.RecoverStationMapper; +import com.ruoyi.service.ConlumnMenuService; +import com.ruoyi.service.IConlumnService; import com.ruoyi.service.impl.PdfParseTaskServiceImpl; import com.ruoyi.system.mapper.SysDeptMapper; import com.ruoyi.system.mapper.SysUserMapper; +import com.ruoyi.util.AsyncUploadService; +import com.ruoyi.util.FileUploadUtil; +import com.ruoyi.util.SftpProperties; import com.sun.xml.bind.v2.TODO; import org.apache.catalina.User; -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.rendering.PDFRenderer; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.springframework.beans.factory.annotation.Value; @@ -42,7 +52,6 @@ import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.enums.BusinessType; -import com.ruoyi.domain.Article; import com.ruoyi.service.ArticleService; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.core.page.TableDataInfo; @@ -80,8 +89,17 @@ public class ArticleController extends BaseController @Autowired private SysDeptMapper sysDeptMapper; + @Autowired + private SftpProperties sftpProperties; + + @Autowired + private AsyncUploadService asyncUploadService; + @Autowired + private ConlumnMenuService conlumnMenuService; + @Autowired + private ServerConfig serverConfig; /** * 判断部门是否直接隶属于荆州市长江河道管理局(一级子部门) * @param dept 部门信息 @@ -316,8 +334,48 @@ public class ArticleController extends BaseController list = articleService.selectArticleColumnList(userName, startTime, endTime); } } - - return getDataTable(list); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(ConlumnMenu::getParentId, 12); + queryWrapper.orderByAsc(ConlumnMenu::getOrderNum); + List list2 = new ArrayList<>(); + list2.add(new ArticleColumnVo("河道文苑", 0)); + List list1 = conlumnMenuService.list(queryWrapper); + for (ConlumnMenu conlumnMenu : list1) { + list2.add(new ArticleColumnVo(conlumnMenu.getName(), 0)); + } + // 1. 将list转换为columnName -> count的Map(快速查询) + Map countMap = list.stream() + .collect(Collectors.toMap( + ArticleColumnVo::getColumnName, // key:栏目名称 + ArticleColumnVo::getCount, // value:对应的count + (existing, replacement) -> replacement // 若columnName重复,取后者 + )); + + // 2. 遍历list2,匹配columnName并更新count + list2.forEach(vo -> { + String columnName = vo.getColumnName(); + if (countMap.containsKey(columnName)) { + vo.setCount(countMap.get(columnName)); // 更新count + } + }); + //在list2中删除缺失元素 + list2.removeIf(item -> item.getColumnName().equals("党建创先")); + // ====== 新增:将list中list2不存在的columnName添加到list2 ====== + // 1. 提取list2中已有的栏目名称(去重) + Set existingColumnNames = list2.stream() + .map(ArticleColumnVo::getColumnName) + .collect(Collectors.toSet()); + + // 2. 遍历list,添加list2中不存在的栏目 + list.forEach(vo -> { + String columnName = vo.getColumnName(); + if (!existingColumnNames.contains(columnName)) { + list2.add(vo); // 添加缺失元素 + existingColumnNames.add(columnName); // 避免重复添加 + } + }); + + return getDataTable(list2); // 返回更新后的list2 } /** @@ -499,7 +557,8 @@ public class ArticleController extends BaseController @Anonymous @GetMapping("/deptArticleCount") public TableDataInfo selectDeptArticleCount(@RequestParam(value = "startTime", defaultValue = "") String startTime, - @RequestParam(value = "endTime", defaultValue = "") String endTime) { + @RequestParam(value = "endTime", defaultValue = "") String endTime, + @RequestParam(value = "columnIds", required = false) List columnIds) { // 权限检查:只有admin用户和市局/总局用户可以访问 LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); SysUser sysUser = loginUser.getUser(); @@ -507,51 +566,45 @@ public class ArticleController extends BaseController String deptName = sysUser.getDept() != null ? sysUser.getDept().getDeptName() : null; boolean hasPermission = "admin".equals(userName) || - (deptName != null && "市局/总局".equals(deptName)) || - (sysUser.getDept() != null && isCityBureauOrSubordinate(sysUser)); + (deptName != null && "市局/总局".equals(deptName)) || + (sysUser.getDept() != null && isCityBureauOrSubordinate(sysUser)); if (!hasPermission) { // 如果没有权限,返回空列表 return getDataTable(new ArrayList<>()); } - List list = articleService.selectDeptArticleCountList(startTime, endTime); + List list = articleService.selectDeptArticleCountList(startTime, endTime, columnIds); return getDataTable(list); } - /** - * 审核表路径 - */ @Anonymous @PostMapping("/appvalPath/{id}") public AjaxResult appvalPath(@PathVariable Long id, @RequestParam("appvalPath") MultipartFile appvalPath) throws Exception { - try { - // 检查文件是否为空 - if (appvalPath.isEmpty()) { - return AjaxResult.error("上传文件为空"); - } - - // 获取 base-path - String basePath = "articleFilePath/appvalPath/"; - - // 指定oss保存文件路径 - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + appvalPath.getOriginalFilename(); - // 上传图片,成功返回文件信息 - FileInfo fileInfo = fileStorageService.of(appvalPath).setPath(objectName).upload(); + // 检查文件是否为空 + if (appvalPath.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } - // 设置返回结果 + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, appvalPath); + String url = serverConfig.getUrl() + fileName; + url = url.replaceFirst("http://", "https://"); AjaxResult ajax = AjaxResult.success(); - ajax.put("url", fileInfo.getUrl()); - ajax.put("fileName", fileInfo.getUrl()); - ajax.put("newFileName", fileInfo.getUrl()); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); ajax.put("originalFilename", appvalPath.getOriginalFilename()); - // 创建Article对象并设置ID和filePath + // 创建ArticleUncheck对象并设置ID和appval Article article = new Article(); article.setArticleId(id); - article.setAppval(fileInfo.getUrl()); + article.setAppval(url); - // 更新数据库中的filePath字段 + // 更新数据库中的appval字段 articleService.updateById(article); return ajax; @@ -560,77 +613,70 @@ public class ArticleController extends BaseController return AjaxResult.error(e.getMessage()); } } - /** - * 文章内容图片上传 + * 文章内容图片上传 - 上传到远程服务器(异步) */ @Anonymous @PostMapping("/contentImage") public AjaxResult uploadContentImage(@RequestParam("file") MultipartFile file) throws Exception { - try { - // 检查文件是否为空 - if (file.isEmpty()) { - return AjaxResult.error("上传文件为空"); - } - - // 获取 base-path - String basePath = "articleFilePath/contentImage/"; - - // 指定oss保存文件路径 - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + file.getOriginalFilename(); - // 上传图片,成功返回文件信息 - FileInfo fileInfo = fileStorageService.of(file).setPath(objectName).upload(); + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } - // 设置返回结果 + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url = url.replaceFirst("http://", "https://"); AjaxResult ajax = AjaxResult.success(); - ajax.put("url", fileInfo.getUrl()); - ajax.put("fileName", fileInfo.getUrl()); - ajax.put("newFileName", fileInfo.getUrl()); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); ajax.put("originalFilename", file.getOriginalFilename()); return ajax; - - } catch (Exception e) { - return AjaxResult.error(e.getMessage()); - } + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } } + + + /** - * 封面路径 + * 封面路径 - 上传到远程服务器(异步) */ @Anonymous @PostMapping("/filePath/{id}") public AjaxResult uploadFile(@PathVariable Long id, @RequestParam MultipartFile file) throws Exception { - try { - // 检查文件是否为空 - if (file.isEmpty()) { - return AjaxResult.error("上传文件为空"); - } - - // 获取 base-path - String basePath = "articleFilePath/filePath/"; // 直接在这里设置基础路径,或者从配置文件中读取 - - // 指定oss保存文件路径 - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + file.getOriginalFilename(); - // 上传图片,成功返回文件信息 - FileInfo fileInfo = fileStorageService.of(file).setPath(objectName).upload(); + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } - // 设置返回结果 + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url = url.replaceFirst("http://", "https://"); AjaxResult ajax = AjaxResult.success(); - ajax.put("url", fileInfo.getUrl()); - ajax.put("fileName", fileInfo.getUrl()); - ajax.put("newFileName", fileInfo.getUrl()); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); ajax.put("originalFilename", file.getOriginalFilename()); - // 创建Article对象并设置ID和filePath + // 创建ArticleUncheck对象并设置ID和coverPath Article article = new Article(); article.setArticleId(id); - article.setCoverPath(fileInfo.getUrl()); - System.out.printf("封面路径:"+fileInfo.getUrl()); - - // 更新数据库中的filePath字段 - articleMapper.updateArticle(article); + article.setCoverPath(url); + // 更新数据库中的coverPath字段 + articleService.updateById(article); return ajax; } catch (Exception e) { @@ -639,54 +685,37 @@ public class ArticleController extends BaseController } /** - * 附件上传 + * 附件上传 - 上传到远程服务器(异步) */ @Anonymous @PostMapping("/attachment/{id}") public AjaxResult uploadAttachment(@PathVariable Long id, @RequestParam MultipartFile file) throws Exception { - try { - // 检查文件是否为空 - if (file.isEmpty()) { - return AjaxResult.error("上传文件为空"); - } - - // 获取 base-path - String basePath = "articleFilePath/attachment/"; - - // 指定oss保存文件路径 - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + file.getOriginalFilename(); - // 上传文件,成功返回文件信息 - FileInfo fileInfo = fileStorageService.of(file).setPath(objectName).upload(); + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } - // 设置返回结果 + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url = url.replaceFirst("http://", "https://"); AjaxResult ajax = AjaxResult.success(); - ajax.put("url", fileInfo.getUrl()); - ajax.put("fileName", fileInfo.getUrl()); - ajax.put("newFileName", fileInfo.getUrl()); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); ajax.put("originalFilename", file.getOriginalFilename()); - // 先查询完整的Article对象 + // 先查询完整的ArticleUncheck对象 Article article = articleService.selectArticleByArticleId(id); if (article != null) { // 更新attachment字段 - article.setAttachment(fileInfo.getUrl()); + article.setAttachment(url); // 更新数据库中的attachment字段 articleMapper.updateArticle(article); - - // 如果上传的是PDF文件,触发PDF解析 - String contentType = file.getContentType(); - System.out.println("上传文件的Content-Type: " + contentType); - if (contentType != null && contentType.equals("application/pdf")) { - System.out.println("检测到PDF文件,触发PDF解析任务,文章ID: " + id); - // 异步处理PDF解析任务 - pdfParseTaskService.processPdfParseTask(id); - } else { - System.out.println("不是PDF文件或Content-Type为空,不触发解析"); - } } - - System.out.println("文章附件:"+fileInfo.getUrl()); - return ajax; } catch (Exception e) { @@ -694,202 +723,8 @@ public class ArticleController extends BaseController } } - /** - * 解析PDF文件为图片并保存到pdfImagePaths字段 - */ - private void parsePdfToImages(Article article) { - try { - // 检查文章是否有附件且附件是PDF文件 - String attachmentUrl = article.getAttachment(); - if (attachmentUrl == null || attachmentUrl.isEmpty()) { - return; - } - - // 检查附件URL是否指向PDF文件(简单检查文件扩展名) - if (!attachmentUrl.toLowerCase().endsWith(".pdf")) { - return; - } - - // 从OSS下载PDF文件 - // 注意:这里需要根据您的OSS配置来实现文件下载逻辑 - // 由于直接从URL下载文件可能涉及安全和权限问题,这里提供一个基础实现 - try { - URL url = new URL(attachmentUrl); - // 设置连接超时和读取超时 - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setConnectTimeout(5000); // 5秒连接超时 - connection.setReadTimeout(30000); // 30秒读取超时 - - // 检查响应码 - if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { - try (InputStream inputStream = connection.getInputStream()) { - // 将InputStream转换为MultipartFile或直接处理 - parsePdfInputStreamToImages(article, inputStream); - } - } else { - System.err.println("下载PDF文件失败,HTTP响应码: " + connection.getResponseCode()); - } - connection.disconnect(); - } catch (Exception e) { - System.err.println("下载PDF文件失败: " + e.getMessage()); - } - - } catch (Exception e) { - System.err.println("PDF解析失败: " + e.getMessage()); - // 发生异常时不中断主流程,仅记录日志 - } - } - - /** - * 解析PDF输入流为图片并保存到pdfImagePaths字段 - */ - private void parsePdfInputStreamToImages(Article article, InputStream pdfInputStream) throws IOException { - // 1. PDF转图片(处理所有页) - PDDocument document = PDDocument.load(pdfInputStream); - int totalPages = document.getNumberOfPages(); - if (totalPages == 0) { - document.close(); - return; - } - - PDFRenderer renderer = new PDFRenderer(document); - List imageUrls = new ArrayList<>(); // 存储所有页图片URL - String basePath = "article/pdf-images/"; // OSS基础路径 - String uuid = UUID.randomUUID().toString(); // 生成唯一ID用于关联同PDF的多页图片 - - // 循环处理每一页 - for (int pageNum = 0; pageNum < totalPages; pageNum++) { - // 渲染当前页(1.5f=缩放比例,值越大清晰度越高) - BufferedImage image = renderer.renderImage(pageNum, 1.5f); - - // 2. 图片转为输入流 - ByteArrayOutputStream os = new ByteArrayOutputStream(); - boolean writeSuccess = ImageIO.write(image, "png", os); - if (!writeSuccess) { - document.close(); // 确保关闭文档 - return; - } - InputStream imageInputStream = new ByteArrayInputStream(os.toByteArray()); - - // 3. 上传当前页图片至OSS(文件名包含页号,避免冲突) - String fileName = uuid + "_page" + (pageNum + 1) + ".png"; // 格式:uuid_page1.png - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); - - FileInfo fileInfo = fileStorageService.of(imageInputStream) - .setPlatform("aliyun-oss-1") - .setPath(objectName) - .setOriginalFilename(fileName) - .setContentType("image/png") - .upload(); - - if (fileInfo == null || fileInfo.getUrl() == null) { - document.close(); - return; - } - imageUrls.add(fileInfo.getUrl()); // 直接使用上传后的URL - } - document.close(); // 所有页处理完毕后关闭文档 - - // 4. 存储多图片URL至数据库(逗号分隔) - article.setPdfImagePaths(String.join(",", imageUrls)); - System.out.println("文章PDF图片:" + article.getPdfImagePaths()); - } - - /** - * PDF转图片并上传OSS(支持多页,存储至专用字段pdfImagePaths) - */ - @Anonymous - @PostMapping("/pdfToImage/{id}") - public AjaxResult pdfToImage(@PathVariable Long id, @RequestParam("pdfFile") MultipartFile pdfFile) throws Exception { - try { - // 1. 校验文件 - if (pdfFile.isEmpty()) { - return AjaxResult.error("上传文件为空"); - } - String contentType = pdfFile.getContentType(); - if (contentType == null || !contentType.equals("application/pdf")) { - return AjaxResult.error("请上传PDF格式文件"); - } - - // 2. PDF转图片(处理所有页) - PDDocument document = PDDocument.load(pdfFile.getInputStream()); - int totalPages = document.getNumberOfPages(); - if (totalPages == 0) { - document.close(); - return AjaxResult.error("PDF文件无内容"); - } - - PDFRenderer renderer = new PDFRenderer(document); - List imageUrls = new ArrayList<>(); // 存储所有页图片URL - String basePath = "article/pdf-images/"; // OSS基础路径 - String uuid = UUID.randomUUID().toString(); // 生成唯一ID用于关联同PDF的多页图片 - - // 循环处理每一页 - for (int pageNum = 0; pageNum < totalPages; pageNum++) { - // 渲染当前页(1.5f=缩放比例,值越大清晰度越高) - BufferedImage image = renderer.renderImage(pageNum, 1.5f); - - // 3. 图片转为输入流 - ByteArrayOutputStream os = new ByteArrayOutputStream(); - boolean writeSuccess = ImageIO.write(image, "png", os); - if (!writeSuccess) { - document.close(); // 确保关闭文档 - return AjaxResult.error("第" + (pageNum + 1) + "页图片转换失败"); - } - InputStream imageInputStream = new ByteArrayInputStream(os.toByteArray()); - - // 4. 上传当前页图片至OSS(文件名包含页号,避免冲突) - String fileName = uuid + "_page" + (pageNum + 1) + ".png"; // 格式:uuid_page1.png - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); - - FileInfo fileInfo = fileStorageService.of(imageInputStream) - .setPlatform("aliyun-oss-1") - .setPath(objectName) - .setOriginalFilename(fileName) - .setContentType("image/png") - .upload(); - - if (fileInfo == null || fileInfo.getUrl() == null) { - document.close(); - return AjaxResult.error("第" + (pageNum + 1) + "页图片上传OSS失败"); - } - /*// 5. 处理URL:截取 .png 后的多余字符 - String originalUrl = fileInfo.getUrl(); - int pngIndex = originalUrl.lastIndexOf(".png"); - if (pngIndex != -1) { - String correctedUrl = originalUrl.substring(0, pngIndex + 4); // 保留 ".png" - imageUrls.add(correctedUrl); - } else { - imageUrls.add(originalUrl); // 异常情况直接使用原始URL - }*/ - imageUrls.add(fileInfo.getUrl()); // 直接使用上传后的URL - } - document.close(); // 所有页处理完毕后关闭文档 - - // 5. 存储多图片URL至数据库(逗号分隔,或JSON格式) - Article article = articleService.getById(id); - if (article == null) { - return AjaxResult.error("文章不存在"); - } - // 多URL用逗号分隔存储(或使用JSON格式:String.join(",", imageUrls) 可改为 new Gson().toJson(imageUrls)) - article.setPdfImagePaths(String.join(",", imageUrls)); - System.out.println("文章PDF图片:"+article.getPdfImagePaths()); - boolean updateResult = articleService.updateById(article); - if (!updateResult) { - return AjaxResult.error("图片路径更新数据库失败"); - } - - // 6. 返回所有图片URL - AjaxResult ajax = AjaxResult.success(); - ajax.put("pdfImageUrls", imageUrls); // 前端可通过数组获取所有页图片 - ajax.put("message", "PDF多页转图片成功,共" + totalPages + "页"); - return ajax; - - } catch (Exception e) { - return AjaxResult.error("处理失败: " + e.getMessage()); - } - } - + + // ... 其他现有代码 ... /** @@ -897,26 +732,26 @@ public class ArticleController extends BaseController */ // @PreAuthorize("@ss.hasPermi('article:article:getOneByColumnId')") @GetMapping("/getListByColumnId") - public AjaxResult getListByColumnId(@RequestParam("column_id") Integer columnId, + public TableDataInfo getListByColumnId(@RequestParam("column_id") Integer columnId, @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum, @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) { - if (Objects.isNull(columnId)) { - return error("未知参数"); - } - - // 启用分页 - PageHelper.startPage(pageNum, pageSize); - // 查询数据列表 List
articleList = articleService.query() + .eq("status", 0) .eq("column_id", columnId) + .orderByDesc("pubdate") .list(); - + List
list = articleList.stream().skip((pageNum - 1) * pageSize).limit(pageSize).collect(Collectors.toList()); + //List
articleList = articleService.selectArticleListByColumnId(columnId); // 用PageInfo对结果进行包装 - PageInfo
pageInfo = new PageInfo<>(articleList); - + int num = articleList.size(); + TableDataInfo dataInfo = new TableDataInfo(); + dataInfo.setRows(list); + dataInfo.setTotal(num); + dataInfo.setCode(HttpStatus.SUCCESS); +// PageInfo
pageInfo = new PageInfo<>(list); // 返回分页结果,包含total等信息 - return success(pageInfo); + return dataInfo; } /** * 查询article列表(权限版) @@ -1062,7 +897,6 @@ public class ArticleController extends BaseController - //获取用户ip地址 String ip = sysUser.getLoginIp(); //System.out.println("目前用户的ip:"+ ip); @@ -1225,4 +1059,18 @@ public class ArticleController extends BaseController } } + @GetMapping("/getArticleAll") + @Anonymous + public TableDataInfo getArticleAll(String title) { + startPage(); + LambdaQueryWrapper
queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.and(wrapper -> wrapper.like(title!=null,Article::getTitle,title) + .or() + .like(title!=null,Article::getArticleOrigin,title)); + queryWrapper.eq(Article::getStatus,0); + queryWrapper.orderByDesc(Article::getPubdate); + List
articles = articleService.list(queryWrapper); + return getDataTable(articles); + } + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/ArticleUncheckController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/ArticleUncheckController.java index b183116987c73196e74d9274061c5a8edd7d4cc7..f79ced917a8cb7007a2f102bdbee95a8c49ed3ae 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/ArticleUncheckController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/ArticleUncheckController.java @@ -7,22 +7,31 @@ import java.time.format.DateTimeFormatter; import java.util.*; import javax.servlet.http.HttpServletResponse; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.common.annotation.Anonymous; import com.github.pagehelper.PageInfo; +import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.domain.Article; import com.ruoyi.domain.RecoverStation; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.mapper.ArticleMapper; import com.ruoyi.mapper.ArticleUncheckMapper; +import com.ruoyi.service.impl.PdfParseTaskServiceImpl; import com.ruoyi.system.mapper.SysDeptMapper; +import com.ruoyi.util.FileUploadUtil; +import com.ruoyi.util.SftpProperties; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; @@ -62,39 +71,44 @@ public class ArticleUncheckController extends BaseController @Autowired private SysDeptMapper sysDeptMapper; + @Autowired + private PdfParseTaskServiceImpl pdfParseTaskService; + + @Autowired + private SftpProperties sftpProperties; + + @Autowired + private ServerConfig serverConfig; /** - * 封面路径 + * 封面路径 - 上传到远程服务器 */ @Anonymous @PostMapping("/filePath/{id}") public AjaxResult coverPath(@PathVariable Long id, @RequestParam MultipartFile file) throws Exception { - try { - // 检查文件是否为空 - if (file.isEmpty()) { - return AjaxResult.error("上传文件为空"); - } - - // 获取 base-path - String basePath = "uncheckFiles/coverPath/"; // 直接在这里设置基础路径,或者从配置文件中读取 - - // 指定oss保存文件路径 - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + file.getOriginalFilename(); - // 上传图片,成功返回文件信息 - FileInfo fileInfo = fileStorageService.of(file).setPath(objectName).upload(); + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } - // 设置返回结果 + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url = url.replaceFirst("http://", "https://"); AjaxResult ajax = AjaxResult.success(); - ajax.put("url", fileInfo.getUrl()); - ajax.put("fileName", fileInfo.getUrl()); // 这里的值要改为url,前端访问的地址,需要文件的地址 而不是文件名称 - ajax.put("newFileName", fileInfo.getUrl()); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); ajax.put("originalFilename", file.getOriginalFilename()); - // 创建Article对象并设置ID和filePath + // 创建ArticleUncheck对象并设置ID和coverPath ArticleUncheck articleUncheck = new ArticleUncheck(); articleUncheck.setArticleId(id); - articleUncheck.setCoverPath(fileInfo.getUrl()); + articleUncheck.setCoverPath(url); - // 更新数据库中的filePath字段 + // 更新数据库中的coverPath字段 articleUncheckService.updateArticleUncheck(articleUncheck); return ajax; @@ -104,37 +118,34 @@ public class ArticleUncheckController extends BaseController } /** - * 附件上传 + * 附件上传 - 上传到远程服务器 */ @Anonymous @PostMapping("/attachment/{id}") public AjaxResult uploadAttachment(@PathVariable Long id, @RequestParam MultipartFile file) throws Exception { - try { - // 检查文件是否为空 - if (file.isEmpty()) { - return AjaxResult.error("上传文件为空"); - } - - // 获取 base-path - String basePath = "uncheckFiles/attachment/"; - - // 指定oss保存文件路径 - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + file.getOriginalFilename(); - // 上传文件,成功返回文件信息 - FileInfo fileInfo = fileStorageService.of(file).setPath(objectName).upload(); + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } - // 设置返回结果 + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url = url.replaceFirst("http://", "https://"); AjaxResult ajax = AjaxResult.success(); - ajax.put("url", fileInfo.getUrl()); - ajax.put("fileName", fileInfo.getUrl()); - ajax.put("newFileName", fileInfo.getUrl()); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); ajax.put("originalFilename", file.getOriginalFilename()); // 先查询完整的ArticleUncheck对象 ArticleUncheck articleUncheck = articleUncheckService.selectArticleUncheckByArticleId(id); if (articleUncheck != null) { // 更新attachment字段 - articleUncheck.setAttachment(fileInfo.getUrl()); + articleUncheck.setAttachment(url); // 更新数据库中的attachment字段 articleUncheckService.updateArticleUncheck(articleUncheck); } @@ -146,38 +157,35 @@ public class ArticleUncheckController extends BaseController } /** - * 审核表路径 + * 审核表路径 - 上传到远程服务器 */ @Anonymous @PostMapping("/appvalPath/{id}") public AjaxResult appvalPath(@PathVariable Long id, @RequestParam("appvalPath") MultipartFile appvalPath) throws Exception { - try { - // 检查文件是否为空 - if (appvalPath.isEmpty()) { - return AjaxResult.error("上传文件为空"); - } - - // 获取 base-path - String basePath = "uncheckFiles/appvalPath/"; - - // 指定oss保存文件路径 - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + appvalPath.getOriginalFilename(); - // 上传图片,成功返回文件信息 - FileInfo fileInfo = fileStorageService.of(appvalPath).setPath(objectName).upload(); + // 检查文件是否为空 + if (appvalPath.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } - // 设置返回结果 + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, appvalPath); + String url = serverConfig.getUrl() + fileName; + url = url.replaceFirst("http://", "https://"); AjaxResult ajax = AjaxResult.success(); - ajax.put("url", fileInfo.getUrl()); - ajax.put("fileName", fileInfo.getUrl()); // 注意:这里的值要改为url,前端访问的地址,需要文件的地址 而不是文件名称 - ajax.put("newFileName", fileInfo.getUrl()); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); ajax.put("originalFilename", appvalPath.getOriginalFilename()); - // 创建Article对象并设置ID和filePath + // 创建ArticleUncheck对象并设置ID和appval ArticleUncheck articleUncheck = new ArticleUncheck(); articleUncheck.setArticleId(id); - articleUncheck.setAppval(fileInfo.getUrl()); + articleUncheck.setAppval(url); - // 更新数据库中的filePath字段 + // 更新数据库中的appval字段 articleUncheckService.updateArticleUncheck(articleUncheck); return ajax; @@ -188,29 +196,26 @@ public class ArticleUncheckController extends BaseController } /** - * 文章内容图片上传并替换为OSS路径 + * 文章内容图片上传 - 上传到远程服务器 */ @PostMapping("/contentImage") public AjaxResult uploadContentImage(@RequestParam("file") MultipartFile file) throws Exception { - try { - // 检查文件是否为空 - if (file.isEmpty()) { - return AjaxResult.error("上传文件为空"); - } - - // 获取 base-path - String basePath = "articleContent/images/"; - - // 指定oss保存文件路径 - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + file.getOriginalFilename(); - // 上传图片,成功返回文件信息 - FileInfo fileInfo = fileStorageService.of(file).setPath(objectName).upload(); + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } - // 设置返回结果 + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url = url.replaceFirst("http://", "https://"); AjaxResult ajax = AjaxResult.success(); - ajax.put("url", fileInfo.getUrl()); - ajax.put("fileName", fileInfo.getUrl()); - ajax.put("newFileName", fileInfo.getUrl()); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); ajax.put("originalFilename", file.getOriginalFilename()); return ajax; @@ -227,7 +232,22 @@ public class ArticleUncheckController extends BaseController @PostMapping("/audit") public AjaxResult audit(@RequestBody Article article){ article.setAuditId(article.getArticleId()); - return toAjax(articleMapper.insertArticle(article)); + if (article.getArticleOrigin() == null || article.getArticleOrigin().isEmpty()) { + article.setArticleOrigin("市局/总局"); + } + // 先插入文章 + int result = articleMapper.insertArticle(article); + + // 如果文章有PDF附件,异步解析PDF为图片 + if (result > 0 && article.getAttachment() != null && !article.getAttachment().isEmpty()) { + // 检查附件是否为PDF文件 + if (article.getAttachment().toLowerCase().endsWith(".pdf")) { + // 异步处理PDF解析任务 + pdfParseTaskService.processPdfParseTask(article.getArticleId()); + } + } + + return toAjax(result); } //修改已审核数据,更新article @@ -490,19 +510,28 @@ public class ArticleUncheckController extends BaseController if (Objects.isNull(articleUncheck)){ return error("请将数据填写完整"); } + if(articleUncheck.getSource()==null){ + return error("请填写文章来源"); + } + //************************************************************************ + System.out.println(articleUncheck); LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); SysUser sysUser = loginUser.getUser(); String userName = sysUser.getUserName(); String ip = sysUser.getLoginIp(); articleUncheck.setIp(ip); + articleUncheck.setUpdateTime(new Date()); if (articleUncheck.getAuthor()!=null) articleUncheck.setAuthor(articleUncheck.getAuthor()); else articleUncheck.setAuthor(userName); - + if(sysUser.getDeptId()==101){ + articleUncheck.setArticleOrigin("市局/总局"); + } // 所有用户新增文章时都设置为未审核状态 - articleUncheck.setState("0"); - + if(articleUncheck.getState()==null){ + articleUncheck.setState("0"); + } return toAjax(articleUncheckService.insertArticleUncheck(articleUncheck)); } @@ -525,6 +554,7 @@ public class ArticleUncheckController extends BaseController if ("1".equals(originalState)) { articleUncheck.setState("0"); // 已审核 -> 未审核 articleUncheck.setClearPubdate(true); // 清除审核日期 + // 删除对应的已审核文章 articleMapper.deleteArticleByAuditId(articleUncheck.getArticleId()); } @@ -534,16 +564,30 @@ public class ArticleUncheckController extends BaseController } } // 如果是审核操作(state为1),则直接使用传入的状态值 - + LambdaQueryWrapper
queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(articleUncheck.getTitle()!=null,Article::getTitle, articleUncheck.getTitle()); + articleMapper.delete(queryWrapper); return toAjax(articleUncheckService.updateArticleUncheck(articleUncheck)); } + @PutMapping("/updateState") + public AjaxResult updateState(@RequestBody ArticleUncheck articleUncheck) { + if (articleUncheck.getRemark()==null){ + return error("请输入备注"); + } + articleUncheck.setState("2"); + if (articleUncheckService.updateArticleUncheck(articleUncheck)<0){ + return error("更新失败"); + } + return success("更新成功"); + } /** * 删除uncheck */ @PreAuthorize("@ss.hasPermi('uncheck:uncheck:remove')") @Log(title = "uncheck", businessType = BusinessType.DELETE) @DeleteMapping("/{articleIds}") + @Transactional public AjaxResult remove(@PathVariable Long[] articleIds) { return toAjax(articleUncheckService.deleteArticleUncheckByArticleIds(articleIds)); diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/ChartColumnController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/ChartColumnController.java new file mode 100644 index 0000000000000000000000000000000000000000..90ebf4f1e004dfa64eb54cc4bf1fb74f62789146 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/ChartColumnController.java @@ -0,0 +1,108 @@ +package com.ruoyi.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.framework.config.ServerConfig; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.domain.ChartColumn; +import com.ruoyi.service.IChartColumnService; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.page.TableDataInfo; + +/** + * 首页图表栏目动态显示Controller + * + * @author ya + * @date 2025-09-29 + */ +@RestController +@RequestMapping("/chartColumn/chartColumn") +public class ChartColumnController extends BaseController +{ + @Autowired + private IChartColumnService chartColumnService; + + @Autowired + private ServerConfig serverConfig; + /** + * 查询首页图表栏目动态显示列表 + */ +// @PreAuthorize("@ss.hasPermi('chartColumn:chartColumn:list')") + @GetMapping("/list") + public TableDataInfo list(ChartColumn chartColumn) + { + //startPage(); + List list = chartColumnService.selectChartColumnList(chartColumn); + return getDataTable(list); + } + + /** + * 导出首页图表栏目动态显示列表 + */ + @PreAuthorize("@ss.hasPermi('chartColumn:chartColumn:export')") + @Log(title = "首页图表栏目动态显示", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, ChartColumn chartColumn) + { + List list = chartColumnService.selectChartColumnList(chartColumn); + ExcelUtil util = new ExcelUtil(ChartColumn.class); + util.exportExcel(response, list, "首页图表栏目动态显示数据"); + } + + /** + * 获取首页图表栏目动态显示详细信息 + */ + @PreAuthorize("@ss.hasPermi('chartColumn:chartColumn:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) + { + return success(chartColumnService.selectChartColumnById(id)); + } + + /** + * 新增首页图表栏目动态显示 + */ + @PreAuthorize("@ss.hasPermi('chartColumn:chartColumn:add')") + @Log(title = "首页图表栏目动态显示", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody ChartColumn chartColumn) + { + return toAjax(chartColumnService.insertChartColumn(chartColumn)); + } + + /** + * 修改首页图表栏目动态显示 + */ + @PreAuthorize("@ss.hasPermi('chartColumn:chartColumn:edit')") + @Log(title = "首页图表栏目动态显示", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody ChartColumn chartColumn) + { + return toAjax(chartColumnService.updateChartColumn(chartColumn)); + } + + /** + * 删除首页图表栏目动态显示 + */ + @PreAuthorize("@ss.hasPermi('chartColumn:chartColumn:remove')") + @Log(title = "首页图表栏目动态显示", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) + { + return toAjax(chartColumnService.deleteChartColumnByIds(ids)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/ColumnManagementController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/ColumnManagementController.java new file mode 100644 index 0000000000000000000000000000000000000000..369540cce0ca6eba7649a8f3ccfeb7ccf4ea7e6f --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/ColumnManagementController.java @@ -0,0 +1,28 @@ +package com.ruoyi.controller; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.domain.ColumnManagement; +import com.ruoyi.service.ColumnManagementService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/columnManagement") +public class ColumnManagementController { + @Autowired + private ColumnManagementService columnManagementService; + + @GetMapping("/getColumns/list") + public AjaxResult getColumnsList() { + return AjaxResult.success(columnManagementService.list()); + } + @PutMapping("/updateColumn") + public AjaxResult updateColumn(@RequestBody ColumnManagement columnManagement) { + return AjaxResult.success(columnManagementService.updateById(columnManagement)); + } + @PostMapping("/addColumn") + public AjaxResult addColumn(@RequestBody ColumnManagement columnManagement) { + return AjaxResult.success(columnManagementService.save(columnManagement)); + } +} + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/ColumnPermissionController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/ColumnPermissionController.java index e9aaffc8dc63d527b22fc05b6144ac49f33eae00..9952028dd428f8b671c815e0cfa00754ec26549f 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/ColumnPermissionController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/ColumnPermissionController.java @@ -8,6 +8,7 @@ import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.domain.Conlumn; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.service.IConlumnService; import com.ruoyi.system.mapper.SysDeptMapper; @@ -52,6 +53,8 @@ public class ColumnPermissionController extends BaseController @Autowired private IConlumnService conlumnService; + @Autowired + private ServerConfig serverConfig; /** * 查询栏目临时权限列表 */ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/ConlumnController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/ConlumnController.java index e1128f6a7184c9a1b8a5e8a042662334f7df3c3e..c69e15a800c1ea9066183ebc4d5f2b46686edc3d 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/ConlumnController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/ConlumnController.java @@ -9,15 +9,21 @@ import javax.servlet.http.HttpServletResponse; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.domain.Article; import com.ruoyi.domain.ConlumnMenu; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.mapper.ConlumnMapper; import com.ruoyi.service.ConlumnMenuService; import com.ruoyi.system.mapper.SysDeptMapper; +import com.ruoyi.util.FileUploadUtil; +import com.ruoyi.util.SftpProperties; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiOperation; import org.dromara.x.file.storage.core.FileInfo; @@ -66,6 +72,11 @@ public class ConlumnController extends BaseController @Autowired private FileStorageService fileStorageService;//注入实列 + @Autowired + private SftpProperties sftpProperties; + + @Autowired + private ServerConfig serverConfig; /** @@ -217,7 +228,6 @@ public class ConlumnController extends BaseController public TableDataInfo getColumnListByRole() { LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); SysUser sysUser = loginUser.getUser(); - // 使用基于部门的权限判断 boolean hasManagerPermission = hasManagerPermission(sysUser); Long deptId = sysUser.getDept() != null ? sysUser.getDept().getDeptId() : null; @@ -226,6 +236,14 @@ public class ConlumnController extends BaseController return getDataTable(list); } + @GetMapping( "/listMeColumn") + public TableDataInfo listMeColumn() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(Conlumn::getRole,"1"); + List list=conlumnService.list(queryWrapper); + return getDataTable(list); + } + /** * 导出栏目管理列表 */ @@ -251,6 +269,11 @@ public class ConlumnController extends BaseController return success(conlumnService.getById(id)); } + @GetMapping( "/getConlumnMe/{id}") + public AjaxResult getConlumn(@PathVariable("id") Integer id) { + return AjaxResult.success(conlumnMenuService.getById(id)); + } + @GetMapping("/listMe") public AjaxResult list() { return AjaxResult.success(conlumnMenuService.list()); @@ -273,7 +296,10 @@ public class ConlumnController extends BaseController @Log(title = "栏目管理", businessType = BusinessType.INSERT) @PostMapping("/addMe") public AjaxResult addMenu(@RequestBody ConlumnMenu conlumnMenu) { - return AjaxResult.success(conlumnMenuService.save(conlumnMenu)); + if (conlumnMenuService.save(conlumnMenu)){ + return AjaxResult.success(conlumnMenu.getId()); + } + return AjaxResult.error("添加失败"); } @@ -355,11 +381,57 @@ public class ConlumnController extends BaseController return toAjax(conlumnService.deleteConlumnByIds(ids)); } /** - * 封面路径 + * 封面路径 - 上传到远程服务器 */ @Anonymous @PostMapping("/filePathImg1/{id}/{type}") public AjaxResult uploadFile(@PathVariable int id, @RequestParam MultipartFile file, @PathVariable int type) throws Exception { + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } + + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url = url.replaceFirst("http://", "https://"); + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); + ajax.put("originalFilename", file.getOriginalFilename()); + + // 更新栏目菜单的图片路径 + ConlumnMenu byId = conlumnMenuService.getById(id); + if (byId == null){ + return AjaxResult.error("栏目不存在"); + } + + // 根据type设置对应的图片字段 + if (type == 1){ + byId.setImg1(url); + }else if (type == 2){ + byId.setImg2(url); + } + + // 更新数据库记录 + boolean updateResult = conlumnMenuService.updateById(byId); + if (!updateResult) { + return AjaxResult.error("图片路径保存失败"); + } + + log.info("栏目图片上传成功,路径:" + url); + return ajax; + + } catch (Exception e) { + log.error("栏目图片上传异常", e); + return AjaxResult.error("上传失败: " + e.getMessage()); + } + + /* 原OSS上传代码 - 已注释 try { // 检查文件是否为空 if (file.isEmpty()) { @@ -416,6 +488,7 @@ public class ConlumnController extends BaseController log.error("栏目图片上传异常", e); return AjaxResult.error("上传失败: " + e.getMessage()); } + */ } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/DangerController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/DangerController.java index 98d8ed41c6052040043d8044ad9e8fe418d97f07..57902b7abf724633a06582108d845459573889bb 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/DangerController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/DangerController.java @@ -2,6 +2,7 @@ package com.ruoyi.controller; import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.service.impl.DangerService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -12,6 +13,8 @@ import org.springframework.web.multipart.MultipartFile; public class DangerController { @Autowired DangerService dangerService; + @Autowired + private ServerConfig serverConfig; @Anonymous @PostMapping("/filePath/{id}") public AjaxResult uploadFile(@PathVariable int id, @RequestParam MultipartFile file) throws Exception { diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/DraftArticleController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/DraftArticleController.java index d63136d55ade4b4bc8ec15f1c68a5e1d8ddffc6c..17b12757bd3cb42cd62d4ae51774f92984cdfed8 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/DraftArticleController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/DraftArticleController.java @@ -8,6 +8,7 @@ import javax.servlet.http.HttpServletResponse; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.framework.web.service.TokenService; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; @@ -37,6 +38,8 @@ public class DraftArticleController extends BaseController @Autowired private TokenService tokenService; + @Autowired + private ServerConfig serverConfig; /** * 查询草稿文章列表 */ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/DutyController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/DutyController.java index 863743ad23a603c986f6306d5bf4849e75949f9a..2ce0f3fe1ace02a604a3ad08f8dd5bd069e1e975 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/DutyController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/DutyController.java @@ -4,6 +4,7 @@ import java.util.List; import javax.servlet.http.HttpServletResponse; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.framework.config.ServerConfig; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -36,6 +37,8 @@ public class DutyController extends BaseController @Autowired private IDutyService dutyService; + @Autowired + private ServerConfig serverConfig; /** * 查询duty列表 */ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/HistoryController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/HistoryController.java index 74b535964b8fd1e8dd8fa6905b477b18723ad803..1f388a8e52add16d7e66bea18b8449ec9f861d29 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/HistoryController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/HistoryController.java @@ -6,7 +6,13 @@ import java.util.List; import javax.servlet.http.HttpServletResponse; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.domain.Article; +import com.ruoyi.framework.config.ServerConfig; +import com.ruoyi.util.FileUploadUtil; +import com.ruoyi.util.SftpProperties; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.springframework.security.access.prepost.PreAuthorize; @@ -38,13 +44,51 @@ public class HistoryController extends BaseController @Autowired private FileStorageService fileStorageService; + @Autowired + private SftpProperties sftpProperties; + + @Autowired + private ServerConfig serverConfig; /** - * 图片路径 + * 图片路径 - 上传到远程服务器 */ @Anonymous @PostMapping("/imgPath/{id}") public AjaxResult imgPath(@PathVariable Long id, @RequestParam("imgPath") MultipartFile imgPath) throws Exception { + // 检查文件是否为空 + if (imgPath.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } + + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, imgPath); + String url = serverConfig.getUrl() + fileName; + url=url.replaceAll("http://", "https://"); + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); + ajax.put("originalFilename", imgPath.getOriginalFilename()); + + // 创建History对象并设置ID和img + History history = new History(); + history.setId(id); + history.setImg(url); + + // 更新数据库中的img字段 + historyService.updateHistory(history); + + return ajax; + + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + + /* 原OSS上传代码 - 已注释 try { // 检查文件是否为空 if (imgPath.isEmpty()) { @@ -79,6 +123,7 @@ public class HistoryController extends BaseController } catch (Exception e) { return AjaxResult.error(e.getMessage()); } + */ } /** diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/HomePageController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/HomePageController.java index f07db943c9c8c7a44c3e425e64d501aa1a653980..f4054798653318a2969230c322ac59bca3c6e621 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/HomePageController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/HomePageController.java @@ -7,6 +7,7 @@ import javax.servlet.http.HttpServletResponse; import com.ruoyi.domain.Article; import com.ruoyi.domain.HomePage; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.service.ArticleService; import com.ruoyi.service.IHomePageService; import org.springframework.security.access.prepost.PreAuthorize; @@ -41,6 +42,8 @@ public class HomePageController extends BaseController private IHomePageService homePageService; @Autowired private ArticleService articleService; + @Autowired + private ServerConfig serverConfig; /** * 查询首页轮播图列表 */ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/Keshi1Controller.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/Keshi1Controller.java index c6d4058c8c4a15322ec257ac79e2626e97e09c0d..293d6435291d404cf4fd96da08bc49293ffde109 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/Keshi1Controller.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/Keshi1Controller.java @@ -5,6 +5,7 @@ import javax.servlet.http.HttpServletResponse; import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.domain.Keshi1; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.service.IKeshi1Service; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; @@ -37,6 +38,8 @@ public class Keshi1Controller extends BaseController @Autowired private IKeshi1Service keshi1Service; + @Autowired + private ServerConfig serverConfig; /** * 查询科室1列表 */ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/Keshi2Controller.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/Keshi2Controller.java index 7978153838d03c6d39fa87a006e80f7afe7a36aa..29f6e38429a5e19c2538e1c163e7bfb82709ce00 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/Keshi2Controller.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/Keshi2Controller.java @@ -4,6 +4,7 @@ import java.util.List; import javax.servlet.http.HttpServletResponse; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.framework.config.ServerConfig; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -36,6 +37,8 @@ public class Keshi2Controller extends BaseController @Autowired private IKeshi2Service keshi2Service; + @Autowired + private ServerConfig serverConfig; /** * 查询科室2列表 全部 */ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/KindController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/KindController.java index c6bbee27f194051c3ab14f79022b151c955d9dcf..8b5a77ecd83e1b0bd1036a43e3fabae668ab5318 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/KindController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/KindController.java @@ -8,6 +8,7 @@ import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.domain.Kind; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.service.IKindService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; @@ -29,6 +30,8 @@ public class KindController extends BaseController @Autowired private IKindService kindService; + @Autowired + private ServerConfig serverConfig; /** * 查询审批类型列表 */ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/LeaderController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/LeaderController.java index 92871377b3f6ee4f3fd258ed81c2e33f19925972..139eaf5a696f7f1da3fead072ed19636b6ccf4b6 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/LeaderController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/LeaderController.java @@ -6,7 +6,13 @@ import java.util.List; import javax.servlet.http.HttpServletResponse; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.domain.Article; +import com.ruoyi.framework.config.ServerConfig; +import com.ruoyi.util.FileUploadUtil; +import com.ruoyi.util.SftpProperties; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.springframework.security.access.prepost.PreAuthorize; @@ -38,9 +44,53 @@ public class LeaderController extends BaseController @Autowired private FileStorageService fileStorageService;//注入实列 + @Autowired + private SftpProperties sftpProperties; + + @Autowired + private ServerConfig serverConfig; + /** + * 领导照片上传 - 上传到远程服务器 + */ @Anonymous @PostMapping("/imagePath/{id}") public AjaxResult uploadFile(@PathVariable Long id, @RequestParam MultipartFile file) throws Exception { + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } + + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url=url.replaceAll("http://", "https://"); + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); + ajax.put("originalFilename", file.getOriginalFilename()); + + // 创建Leader对象并设置ID和image + Leader leader = new Leader(); + leader.setLeaderId(id); + leader.setImage(url); + + // 更新数据库中的image字段 + boolean result = leaderService.updateById(leader); + if (!result) { + return AjaxResult.error("更新文件路径失败"); + } + + return ajax; + + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + + /* 原OSS上传代码 - 已注释 try { // 检查文件是否为空 if (file.isEmpty()) { @@ -78,6 +128,7 @@ public class LeaderController extends BaseController } catch (Exception e) { return AjaxResult.error(e.getMessage()); } + */ } /** diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/PartyclassController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/PartyclassController.java index 164c154444ff36e8689db3999f4430913771a760..e7cecaaa300bc6b02100e7f0f4a102f6b7935585 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/PartyclassController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/PartyclassController.java @@ -4,18 +4,34 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.page.PageDomain; +import com.ruoyi.common.core.page.TableSupport; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.common.utils.uuid.UUID; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.util.AsyncUploadService; +import com.ruoyi.util.ChunkedFileUploadUtil; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.User; import org.springframework.web.bind.annotation.*; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; @@ -46,20 +62,123 @@ public class PartyclassController extends BaseController @Autowired private FileStorageService fileStorageService; + + @Autowired + private ServerConfig serverConfig; + @Value("/data/project") + private String partyclassPath; + + + + /** - * 查询partyclass列表 + * 查询partyclass列表(分局) */ // @PreAuthorize("@ss.hasPermi('partyclass:partyclass:list')") @Anonymous - @GetMapping("/list") - public TableDataInfo list(Partyclass partyclass) + @PostMapping("/parList") + public TableDataInfo list(@RequestBody Partyclass partyclass,boolean isOrder) { + + PageDomain pageDomain = TableSupport.buildPageRequest(); + Integer pageNum = pageDomain.getPageNum(); + Integer pageSize = pageDomain.getPageSize(); + SysUser user = SecurityUtils.getLoginUser().getUser(); startPage(); - List list = partyclassService.selectPartyclassList(partyclass); + List list; + if(isOrder){ + if(user.getDeptId() == 100||user.getDeptId() == 101){ + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.orderByDesc(Partyclass::getSortOrder); + queryWrapper.eq(partyclass.getType()!=null,Partyclass::getType,partyclass.getType()); + queryWrapper.like(partyclass.getName()!=null,Partyclass::getName,partyclass.getName()); + queryWrapper.eq(partyclass.getVideoOrigin()!=null,Partyclass::getVideoOrigin,partyclass.getVideoOrigin()); + queryWrapper.eq(partyclass.getAuthor()!=null,Partyclass::getAuthor,partyclass.getAuthor()); + list=partyclassService.list(queryWrapper); + }else { + partyclass.setUserId(user.getUserId()); + list=partyclassService.selectPartyclassList(partyclass); + } + }else { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (user.getDeptId() == 100 || user.getDeptId() == 101) { + queryWrapper.and(wrapper -> wrapper.eq(Partyclass::getPublish, 0) + .or() + .eq(Partyclass::getPublish, 1)) + .eq(partyclass.getType()!=null,Partyclass::getType,partyclass.getType()) + .like(partyclass.getName()!=null,Partyclass::getName,partyclass.getName()) + .eq(partyclass.getVideoOrigin()!=null,Partyclass::getVideoOrigin,partyclass.getVideoOrigin()) + .eq(partyclass.getAuthor()!=null,Partyclass::getAuthor,partyclass.getAuthor()) + .orderByAsc(Partyclass::getPublish) + .orderByDesc(Partyclass::getPubdate); + list = partyclassService.list(queryWrapper); + } else { + // 普通用户查询逻辑(保持不变) + queryWrapper.eq(Partyclass::getUserId, user.getUserId()) + .and(wrapper -> wrapper.eq(Partyclass::getPublish, 0) + .or() + .eq(Partyclass::getPublish, 1)) + .eq(partyclass.getType()!=null,Partyclass::getType,partyclass.getType()) + .like(partyclass.getName()!=null,Partyclass::getName,partyclass.getName()) + .eq(partyclass.getVideoOrigin()!=null,Partyclass::getVideoOrigin,partyclass.getVideoOrigin()) + .eq(partyclass.getAuthor()!=null,Partyclass::getAuthor,partyclass.getAuthor()) + .orderByAsc(Partyclass::getPublish) + .orderByDesc(Partyclass::getPubdate); + list = partyclassService.list(queryWrapper); + } + } +// //获取处理好的list集合 +// int num = list.size(); +// list = list.stream().skip((pageNum - 1) * pageSize).limit(pageSize).collect(Collectors.toList()); +// +// TableDataInfo rspData = new TableDataInfo(); +// rspData.setCode(HttpStatus.SUCCESS); +// rspData.setRows(list); +// rspData.setTotal(num); +// return rspData; return getDataTable(list); } - +// /** +// * 查询所有视频列表首页面 +// * @return +// */ +// @GetMapping("/list") +// public TableDataInfo allList() +// { +// startPage(); +// SysUser user = SecurityUtils.getLoginUser().getUser(); +// List list=new ArrayList<>(); +// List wList; +// List fList; +// if(user.getDeptId() == 100||user.getDeptId() == 101){ +// LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); +// queryWrapper.orderByDesc(Partyclass::getPubdate); +// queryWrapper.eq(Partyclass::getPublish,0); +// wList=partyclassService.list(queryWrapper); +// LambdaQueryWrapper queryWrapper1 = new LambdaQueryWrapper<>(); +// queryWrapper1.orderByDesc(Partyclass::getPubdate); +// queryWrapper1.eq(Partyclass::getPublish,1); +// fList=partyclassService.list(queryWrapper1); +// list.addAll(wList); +// list.addAll(fList); +// }else { +// LambdaQueryWrapper query = new LambdaQueryWrapper<>(); +// query.orderByDesc(Partyclass::getPubdate); +// query.eq(Partyclass::getPublish,0); +// query.eq(Partyclass::getUserId,user.getUserId()); +// wList=partyclassService.list(query); +// LambdaQueryWrapper queryWrapper1 = new LambdaQueryWrapper<>(); +// queryWrapper1.orderByDesc(Partyclass::getPubdate); +// queryWrapper1.eq(Partyclass::getPublish,1); +// queryWrapper1.eq(Partyclass::getUserId,user.getUserId()); +// fList=partyclassService.list(queryWrapper1); +// list.addAll(wList); +// list.addAll(fList); +// } +// +// return getDataTable(list); +// } /** * 发布视频 */ @@ -143,21 +262,6 @@ public class PartyclassController extends BaseController @RequestParam(value = "type",required = false) int type ) { - System.out.println("=== 接收到文件上传请求 ==="); - System.out.println("name: " + name); - System.out.println("file is null: " + (file == null)); - System.out.println("file is empty: " + (file != null ? file.isEmpty() : "N/A")); - System.out.println("coverImg is null: " + (coverImg == null)); - System.out.println("coverImg is empty: " + (coverImg != null ? coverImg.isEmpty() : "N/A")); - if (file != null && !file.isEmpty()) { - System.out.println("file original name: " + file.getOriginalFilename()); - System.out.println("file size: " + file.getSize()); - } - if (coverImg != null && !coverImg.isEmpty()) { - System.out.println("coverImg original name: " + coverImg.getOriginalFilename()); - System.out.println("coverImg size: " + coverImg.getSize()); - } - Partyclass partyclass = new Partyclass(); if (name != null) partyclass.setName(name); if (x != null) partyclass.setX(x); @@ -166,7 +270,8 @@ public class PartyclassController extends BaseController if (author != null) partyclass.setAuthor(author); if (videoOrigin != null) partyclass.setVideoOrigin(videoOrigin); partyclass.setType(type); - + + partyclass.setUserId(getUserId()); // 设置默认排序值为该类型下的最大值+1 int maxSortOrder = partyclassService.selectMaxSortOrderByType(type); partyclass.setSortOrder(maxSortOrder + 1); @@ -194,8 +299,20 @@ public class PartyclassController extends BaseController if (hasFile) { //有视频文件上传时使用异步上传 try { - byte[] fileBytes = file.getBytes(); - asyncUploadService.asyncUploadPartyFile(insertedPartyclass, fileBytes, file.getOriginalFilename()); +// byte[] fileBytes = file.getBytes(); +// asyncUploadService.asyncUploadPartyFile(insertedPartyclass, fileBytes, file.getOriginalFilename()); + String s = ChunkedFileUploadUtil.uploadFileLocallyInChunks(file, partyclassPath, "partyclass"); + System.out.println("上传视频文件路径: "); + System.out.println(s); + String url = "https://www.jzhd.org.cn"+s; + Partyclass updatePartyclass = new Partyclass(); + updatePartyclass.setId(partyclass.getId()); + updatePartyclass.setFile(url); + // 执行更新 + int result = partyclassService.updatePartyclass(updatePartyclass); + if (result == 0) { + return AjaxResult.error("更新视频文件路径失败"); + } } catch (Exception e) { return AjaxResult.error("上传视频文件失败"); } @@ -204,8 +321,25 @@ public class PartyclassController extends BaseController if (hasCoverImg) { //封面图片也使用异步上传 try { - byte[] coverBytes = coverImg.getBytes(); - asyncUploadService.asyncUploadPartyCoverImg(insertedPartyclass, coverBytes, coverImg.getOriginalFilename()); +// byte[] coverBytes = coverImg.getBytes(); +// asyncUploadService.asyncUploadPartyCoverImg(insertedPartyclass, coverBytes, coverImg.getOriginalFilename()); + // 只更新封面图片字段 + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, coverImg); + String url = serverConfig.getUrl() + fileName; + if (url.startsWith("http://")) { + url = url.replaceFirst("http://", "https://"); + } + Partyclass updatePartyclass = new Partyclass(); + updatePartyclass.setId(partyclass.getId()); + updatePartyclass.setCoverImg(url); + // 执行更新 + int result = partyclassService.updatePartyclass(updatePartyclass); + if (result == 0) { + return AjaxResult.error("更新封面图片路径失败"); + } } catch (Exception e) { return AjaxResult.error("上传封面图片失败: " + e.getMessage()); } @@ -236,16 +370,17 @@ public class PartyclassController extends BaseController @RequestParam(value = "video_origin",required = false) String videoOrigin, @RequestParam(value = "type",required = false) int type, @RequestParam(value = "x",required = false) Double x, - @RequestParam(value = "y",required = false) Double y + @RequestParam(value = "y",required = false) Double y, + @RequestParam(value = "user_id",required = false) Long userId ) { - System.out.println("=== 接收到文件更新请求 ==="); - System.out.println("id: " + id); - System.out.println("name: " + name); - System.out.println("file is null: " + (file == null)); - System.out.println("file is empty: " + (file != null ? file.isEmpty() : "N/A")); - System.out.println("coverImg is null: " + (coverImg == null)); - System.out.println("coverImg is empty: " + (coverImg != null ? coverImg.isEmpty() : "N/A")); +// System.out.println("=== 接收到文件更新请求 ==="); +// System.out.println("id: " + id); +// System.out.println("name: " + name); +// System.out.println("file is null: " + (file == null)); +// System.out.println("file is empty: " + (file != null ? file.isEmpty() : "N/A")); +// System.out.println("coverImg is null: " + (coverImg == null)); +// System.out.println("coverImg is empty: " + (coverImg != null ? coverImg.isEmpty() : "N/A")); if (file != null && !file.isEmpty()) { System.out.println("file original name: " + file.getOriginalFilename()); System.out.println("file size: " + file.getSize()); @@ -263,7 +398,10 @@ public class PartyclassController extends BaseController if (videoOrigin != null) partyclass.setVideoOrigin(videoOrigin); if (x != null) partyclass.setX(x); if (y != null) partyclass.setY(y); + if (userId != null) partyclass.setUserId(userId); + partyclass.setType(type); + partyclass.setPublish(0); //同步处理数据 boolean hasFile = (file != null && !file.isEmpty()); @@ -275,8 +413,28 @@ public class PartyclassController extends BaseController if (hasFile) { //有视频文件上传时使用异步上传 try { - byte[] fileBytes = file.getBytes(); - asyncUploadService.asyncUploadPartyFile(partyclass, fileBytes, file.getOriginalFilename()); +// byte[] fileBytes = file.getBytes(); +// asyncUploadService.asyncUploadPartyFile(partyclass, fileBytes, file.getOriginalFilename()); +// // 上传文件路径 +// String filePath = RuoYiConfig.getUploadPath(); +// // 上传并返回新文件名称 +// String fileName = FileUploadUtils.upload(filePath, file); +// String url = serverConfig.getUrl() + fileName; +// Partyclass updatePartyclass = new Partyclass(); +// updatePartyclass.setId(partyclass.getId()); +// updatePartyclass.setFile(url); + String s = ChunkedFileUploadUtil.uploadFileLocallyInChunks(file, partyclassPath, "partyclass"); + System.out.println("上传视频文件路径: "); + System.out.println(s); + String url = "https://www.jzhd.org.cn"+s; + Partyclass updatePartyclass = new Partyclass(); + updatePartyclass.setId(partyclass.getId()); + updatePartyclass.setFile(url); + // 执行更新 + int result = partyclassService.updatePartyclass(updatePartyclass); + if (result == 0) { + return AjaxResult.error("更新视频文件路径失败"); + } } catch (Exception e) { return AjaxResult.error("上传视频文件失败"); } @@ -285,8 +443,23 @@ public class PartyclassController extends BaseController if (hasCoverImg) { //封面图片也使用异步上传 try { - byte[] coverBytes = coverImg.getBytes(); - asyncUploadService.asyncUploadPartyCoverImg(partyclass, coverBytes, coverImg.getOriginalFilename()); +// byte[] coverBytes = coverImg.getBytes(); +// asyncUploadService.asyncUploadPartyCoverImg(partyclass, coverBytes, coverImg.getOriginalFilename()); + // 上传并返回新文件名称 + String filePath = RuoYiConfig.getUploadPath(); + String fileName = FileUploadUtils.upload(filePath, coverImg); + String url = serverConfig.getUrl() + fileName; + if (url.startsWith("http://")) { + url = url.replaceFirst("http://", "https://"); + } + Partyclass updatePartyclass = new Partyclass(); + updatePartyclass.setId(partyclass.getId()); + updatePartyclass.setCoverImg(url); + // 执行更新 + int result = partyclassService.updatePartyclass(updatePartyclass); + if (result == 0) { + return AjaxResult.error("更新封面图片路径失败"); + } } catch (Exception e) { return AjaxResult.error("上传封面图片失败: " + e.getMessage()); } @@ -339,7 +512,7 @@ public class PartyclassController extends BaseController return success("排序更新成功"); } - + /** * 将指定记录移动到顶部 */ @@ -349,24 +522,28 @@ public class PartyclassController extends BaseController public AjaxResult moveToTop(@PathVariable Long id) { // 获取记录 Partyclass targetPartyclass = partyclassService.selectPartyclassById(id); - + if (targetPartyclass == null) { return AjaxResult.error("记录不存在"); } - + int targetType = targetPartyclass.getType(); int currentSortOrder = targetPartyclass.getSortOrder(); - - // 将该类型下所有排序值小于当前记录的记录都加1 - partyclassService.incrementSortOrderForTypeLessThan(targetType, currentSortOrder); - - // 将目标记录的排序值设为0(最前) - targetPartyclass.setSortOrder(0); + + // 获取该类型下的最大排序值 + int maxSortOrder = partyclassService.selectMaxSortOrderByType(targetType); + + // 将该类型下所有排序值大于当前记录的记录都减1 + partyclassService.decrementSortOrderForTypeGreaterThan(targetType, currentSortOrder); + + // 将目标记录的排序值设为最大排序值(最后) + targetPartyclass.setSortOrder(maxSortOrder); partyclassService.updatePartyclass(targetPartyclass); - + return success("已移至顶部"); } - + + /** * 将指定记录上移一位 */ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/RecoverStationController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/RecoverStationController.java index f7c3842b4e3593e94be4e2b8292743bd13e1cd5e..f7aa2edbce7c507ab7f89cdc08c45f437275a385 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/RecoverStationController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/RecoverStationController.java @@ -7,6 +7,7 @@ import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.system.mapper.SysDeptMapper; import org.springframework.security.access.prepost.PreAuthorize; @@ -48,6 +49,8 @@ public class RecoverStationController extends BaseController @Autowired private SysDeptMapper sysDeptMapper; + @Autowired + private ServerConfig serverConfig; /** * 恢复数据到文章表 diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/RiverNewsController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/RiverNewsController.java index e58599164c672bf64268f479466b00bf38b64bc3..f44194e557b60746438fcec241d6e933338696fa 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/RiverNewsController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/RiverNewsController.java @@ -8,7 +8,11 @@ import javax.servlet.http.HttpServletResponse; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.domain.Article; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.mapper.RiverNewsMapper; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; @@ -44,6 +48,8 @@ RiverNewsController extends BaseController @Autowired private RiverNewsMapper riverNewsMapper; + @Autowired + private ServerConfig serverConfig; /** * 封面路径 @@ -57,26 +63,22 @@ RiverNewsController extends BaseController return AjaxResult.error("上传文件为空"); } - // 获取 base-path - String basePath = "articleFilePath/filePath/"; // 直接在这里设置基础路径,或者从配置文件中读取 - - // 指定oss保存文件路径 - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + file.getOriginalFilename(); - // 上传图片,成功返回文件信息 - FileInfo fileInfo = fileStorageService.of(file).setPath(objectName).upload(); - - // 设置返回结果 + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; AjaxResult ajax = AjaxResult.success(); - ajax.put("url", fileInfo.getUrl()); - ajax.put("fileName", fileInfo.getUrl()); - ajax.put("newFileName", fileInfo.getUrl()); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); ajax.put("originalFilename", file.getOriginalFilename()); // 创建Article对象并设置ID和filePath RiverNews riverNews = new RiverNews(); riverNews.setNewsId(id); - riverNews.setCoverPath(fileInfo.getUrl()); - System.out.printf("封面路径:"+fileInfo.getUrl()); + riverNews.setCoverPath(url); + System.out.printf("封面路径:"+url); // 更新数据库中的filePath字段 riverNewsMapper.updateRiverNewsOwn(riverNews); @@ -142,6 +144,8 @@ RiverNewsController extends BaseController @PostMapping public AjaxResult add(@RequestBody RiverNews riverNews) { + int maxSortOrder = riverNewsService.selectMaxSortOrder(); + riverNews.setSortOrder(maxSortOrder + 1); return toAjax(riverNewsService.insertRiverNews(riverNews)); } @@ -171,7 +175,8 @@ RiverNewsController extends BaseController @RequestParam(value = "remark",required = false) String remark, @RequestParam(value = "littleTitle1",required = false) String littleTitle1, @RequestParam(value = "littleTitle2",required = false) String littleTitle2, - @RequestParam(value = "pubdate",required = false) String pubdate + @RequestParam(value = "pubdate",required = false) String pubdate, + @RequestParam(value = "source",required = false) String source ) { RiverNews riverNews = new RiverNews(); @@ -184,16 +189,22 @@ RiverNewsController extends BaseController riverNews.setLittleTitle1(littleTitle1); riverNews.setLittleTitle2(littleTitle2); riverNews.setPubdate(pubdate); + riverNews.setSource(source); + int maxSortOrder = riverNewsService.selectMaxSortOrder(); + riverNews.setSortOrder(maxSortOrder + 1); // 处理文件上传 if (coverPath != null && !coverPath.isEmpty()) { - String basePath = "articleFilePath/"; - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) - + "/" + coverPath.getOriginalFilename(); - - FileInfo fileInfo = fileStorageService.of(coverPath) - .setPath(objectName) - .upload(); - riverNews.setCoverPath(fileInfo.getUrl()); + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, coverPath); + String url = serverConfig.getUrl() + fileName; + url=url.replaceAll("http://", "https://"); + riverNews.setCoverPath(url); + }catch (Exception e){ + e.printStackTrace(); + } } return toAjax(riverNewsService.insertRiverNewsOwn(riverNews)); } @@ -213,7 +224,8 @@ RiverNewsController extends BaseController @RequestParam(value = "remark",required = false) String remark, @RequestParam(value = "littleTitle1",required = false) String littleTitle1, @RequestParam(value = "littleTitle2",required = false) String littleTitle2, - @RequestParam(value = "pubdate",required = false) String pubdate + @RequestParam(value = "pubdate",required = false) String pubdate, + @RequestParam(value = "source",required = false) String source ) { RiverNews riverNews = new RiverNews(); @@ -227,18 +239,22 @@ RiverNewsController extends BaseController riverNews.setLittleTitle1(littleTitle1); riverNews.setLittleTitle2(littleTitle2); riverNews.setPubdate(pubdate); + riverNews.setSource(source); System.out.println("副标题1:"+riverNews.getLittleTitle1()); System.out.println("副标题2:"+riverNews.getLittleTitle2()); // 处理文件上传 if (coverPath != null && !coverPath.isEmpty()) { - String basePath = "articleFilePath/"; - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) - + "/" + coverPath.getOriginalFilename(); - - FileInfo fileInfo = fileStorageService.of(coverPath) - .setPath(objectName) - .upload(); - riverNews.setCoverPath(fileInfo.getUrl()); + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, coverPath); + String url = serverConfig.getUrl() + fileName; + url=url.replaceAll("http://", "https://"); + riverNews.setCoverPath(url); + }catch (Exception e){ + e.printStackTrace(); + } } return toAjax(riverNewsService.updateRiverNewsOwn(riverNews)); } @@ -299,8 +315,8 @@ RiverNewsController extends BaseController int currentSortOrder = targetRiverNews.getSortOrder(); - // 查找排序值刚好小于当前记录的记录 - RiverNews previousRiverNews = riverNewsService.selectPreviousRiverNews(currentSortOrder); + // 查找排序值刚好大于当前记录的记录 + RiverNews previousRiverNews = riverNewsService.selectNextRiverNews(currentSortOrder); if (previousRiverNews == null) { return AjaxResult.error("已经是第一个了"); @@ -341,9 +357,8 @@ RiverNewsController extends BaseController int currentSortOrder = targetRiverNews.getSortOrder(); - // 查找排序值刚好大于当前记录的记录 - RiverNews nextRiverNews = riverNewsService.selectNextRiverNews(currentSortOrder); - + // 查找排序值刚好小于当前记录的记录 + RiverNews nextRiverNews = riverNewsService.selectPreviousRiverNews(currentSortOrder); if (nextRiverNews == null) { return AjaxResult.error("已经是最后一个了"); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/RiverViewController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/RiverViewController.java index 2ef4ba3c594ba0368ac00869a87b7a7525690550..50cdb92e6829a98a88931b841df3eac08a982456 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/RiverViewController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/RiverViewController.java @@ -6,11 +6,18 @@ import java.util.List; import javax.servlet.http.HttpServletResponse; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.domain.Ai; +import com.ruoyi.domain.Partyclass; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.util.AsyncUploadService; +import com.ruoyi.util.ChunkedFileUploadUtil; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; @@ -42,6 +49,11 @@ public class RiverViewController extends BaseController private AsyncUploadService asyncUploadService; + @Autowired + private ServerConfig serverConfig; + + @Value("/data/project") + private String partyclassPath; /** * 查询river_view列表 */ @@ -113,8 +125,21 @@ public class RiverViewController extends BaseController // 异步上传视频 if (video != null && !video.isEmpty()) { try { - byte[] videoBytes = video.getBytes(); - asyncUploadService.asyncUploadVideo(river_View, videoBytes, video.getOriginalFilename()); +// byte[] videoBytes = video.getBytes(); +// asyncUploadService.asyncUploadVideo(river_View, videoBytes, video.getOriginalFilename()); + String s = ChunkedFileUploadUtil.uploadFileLocallyInChunks(video, partyclassPath, "river_view"); + System.out.println("上传视频文件路径: "); + System.out.println(s); + String url = "https://www.jzhd.org.cn"+s; + river_View.setVideo(url); + // 根据 ID 是否存在决定执行 insert 还是 update + if (river_View.getId() == null || riverViewService.selectRiverViewById(river_View.getId()) == null) { + // 数据库中无此 ID,执行新增 + riverViewService.insertRiverView(river_View); + } else { + // 存在,执行更新 + riverViewService.updateRiverView(river_View); + } } catch (Exception e) { return AjaxResult.error("视频文件读取失败"); } @@ -123,8 +148,21 @@ public class RiverViewController extends BaseController // 异步上传封面图 if (cover_img != null && !cover_img.isEmpty()) { try { - byte[] coverBytes = cover_img.getBytes(); - asyncUploadService.asyncUploadCoverImg(river_View, coverBytes, cover_img.getOriginalFilename()); +// byte[] coverBytes = cover_img.getBytes(); +// asyncUploadService.asyncUploadCoverImg(river_View, coverBytes, cover_img.getOriginalFilename()); + + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, cover_img); + String url = serverConfig.getUrl() + fileName; + url=url.replaceAll("http://", "https://"); + river_View.setCover_img(url); + if (river_View.getId() == null || riverViewService.selectRiverViewById(river_View.getId()) == null) { + riverViewService.insertRiverView(river_View); + } else { + riverViewService.updateRiverView(river_View); + } } catch (Exception e) { return AjaxResult.error("封面图读取失败"); } @@ -155,14 +193,40 @@ public class RiverViewController extends BaseController // 异步上传视频 if (video != null && !video.isEmpty()) { - byte[] videoBytes = video.getBytes(); - asyncUploadService.asyncUploadVideo(up_river_View, videoBytes, video.getOriginalFilename()); +// byte[] videoBytes = video.getBytes(); +// asyncUploadService.asyncUploadVideo(up_river_View, videoBytes, video.getOriginalFilename()); + String s = ChunkedFileUploadUtil.uploadFileLocallyInChunks(video, partyclassPath, "river_view"); + System.out.println("上传视频文件路径: "); + System.out.println(s); + String url = "https://www.jzhd.org.cn"+s; + System.out.println("视频文件路径: " + url); + up_river_View.setVideo(url); + // 根据 ID 是否存在决定执行 insert 还是 update + if (up_river_View.getId() == null || riverViewService.selectRiverViewById(up_river_View.getId()) == null) { + // 数据库中无此 ID,执行新增 + riverViewService.insertRiverView(up_river_View); + } else { + // 存在,执行更新 + riverViewService.updateRiverView(up_river_View); + } } // 异步上传封面图 if (cover_img != null && !cover_img.isEmpty()) { - byte[] coverBytes = cover_img.getBytes(); - asyncUploadService.asyncUploadCoverImg(up_river_View, coverBytes, cover_img.getOriginalFilename()); +// byte[] coverBytes = cover_img.getBytes(); +// asyncUploadService.asyncUploadCoverImg(up_river_View, coverBytes, cover_img.getOriginalFilename()); + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, cover_img); + String url = serverConfig.getUrl() + fileName; + url=url.replaceAll("http://", "https://"); + up_river_View.setCover_img(url); + if (up_river_View.getId() == null || riverViewService.selectRiverViewById(up_river_View.getId()) == null) { + riverViewService.insertRiverView(up_river_View); + } else { + riverViewService.updateRiverView(up_river_View); + } } //没有视频和封面 diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/TextReadController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/TextReadController.java index 6bd82e2d262d865c68495fe088ab66d2881185d7..20997f9565da8a6b613971def0e3821efde230b4 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/TextReadController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/TextReadController.java @@ -4,15 +4,21 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; -import com.ruoyi.domain.Article; -import com.ruoyi.domain.ConlumnMenu; -import com.ruoyi.domain.TextRead; -import com.ruoyi.domain.TextReadDto; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.domain.*; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.service.TextReadService; +import com.ruoyi.util.FileUploadUtil; +import com.ruoyi.util.SftpProperties; +import net.sf.jsqlparser.expression.DateTimeLiteralExpression; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -21,6 +27,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Objects; @@ -35,17 +42,39 @@ public class TextReadController extends BaseController { @Autowired private FileStorageService fileStorageService;//注入实列 - @Anonymous - @GetMapping("/list") - public AjaxResult list(@RequestParam Integer pageNum, @RequestParam Integer pageSize) { - // 启用分页 - PageHelper.startPage(pageNum, pageSize); - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.orderByDesc(TextRead::getId); - List list = textReadService.list(queryWrapper); - PageInfo pageInfo = new PageInfo<>(list); - return AjaxResult.success(pageInfo); + @Autowired + private SftpProperties sftpProperties; + + @Autowired + private ServerConfig serverConfig; + @Anonymous + @GetMapping("/list") + public AjaxResult list(@RequestParam Integer pageNum, @RequestParam Integer pageSize) { + // 启用分页 + PageHelper.startPage(pageNum, pageSize); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.orderByDesc(TextRead::getId) + .select(TextRead::getId, TextRead::getName, TextRead::getCoverPath, TextRead::getPublishTime); + List list = textReadService.list(queryWrapper); + List list1=new ArrayList<>(); + for (TextRead textRead : list) { + TextReadDemo demo=new TextReadDemo(); + BeanUtils.copyProperties(textRead,demo); + list1.add(demo); } + PageInfo pageInfo = new PageInfo<>(list1); + return AjaxResult.success(pageInfo); + } + + @Anonymous + @GetMapping("/endList") + public TableDataInfo endList(){ + startPage(); + LambdaQueryWrapper que=new LambdaQueryWrapper<>(); + que.orderByDesc(TextRead::getId); + List list = textReadService.list(que); + return getDataTable(list); + } @Anonymous @GetMapping("/{id}") @@ -103,11 +132,50 @@ public class TextReadController extends BaseController { return success(pageInfo); } /** - * 封面路径 + * 封面路径 - 上传到远程服务器 */ @Anonymous @PostMapping("/filePathImg1/{id}") public AjaxResult uploadFile(@PathVariable int id, @RequestParam MultipartFile file) throws Exception { + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } + + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url=url.replaceAll("http://", "https://"); + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); + ajax.put("originalFilename", file.getOriginalFilename()); + + // 更新TextRead的封面路径 + TextRead byId = textReadService.getById(id); + if (byId == null){ + return AjaxResult.error("栏目不存在"); + } + byId.setCoverPath(url); + // 更新数据库记录 + boolean updateResult = textReadService.updateById(byId); + if (!updateResult) { + return AjaxResult.error("图片路径保存失败"); + } + + log.info("栏目图片上传成功,路径:" + url); + return ajax; + + } catch (Exception e) { + log.error("栏目图片上传异常", e); + return AjaxResult.error("上传失败: " + e.getMessage()); + } + + /* 原OSS上传代码 - 已注释 try { // 检查文件是否为空 if (file.isEmpty()) { @@ -157,13 +225,39 @@ public class TextReadController extends BaseController { log.error("栏目图片上传异常", e); return AjaxResult.error("上传失败: " + e.getMessage()); } + */ } /** - * 文章内容图片上传 + * 文章内容图片上传 - 上传到远程服务器 */ @Anonymous @PostMapping("/contentImage") public AjaxResult uploadContentImage(@RequestParam("file") MultipartFile file) throws Exception { + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } + + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url=url.replaceAll("http://", "https://"); + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); + ajax.put("originalFilename", file.getOriginalFilename()); + + return ajax; + + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + + /* 原OSS上传代码 - 已注释 try { // 检查文件是否为空 if (file.isEmpty()) { @@ -190,5 +284,6 @@ public class TextReadController extends BaseController { } catch (Exception e) { return AjaxResult.error(e.getMessage()); } + */ } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/ToutiaoController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/ToutiaoController.java index 701813aab3eb2607646ccaa8f2b9af3572eecc7d..bbabd63c68680ccf8cdab4aecdb03eaaaa62c11d 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/ToutiaoController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/ToutiaoController.java @@ -6,7 +6,14 @@ import java.util.List; import javax.servlet.http.HttpServletResponse; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.domain.Article; import com.ruoyi.domain.RiverNews; +import com.ruoyi.framework.config.ServerConfig; +import com.ruoyi.util.FileUploadUtil; +import com.ruoyi.util.SftpProperties; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.springframework.security.access.prepost.PreAuthorize; @@ -38,6 +45,11 @@ public class ToutiaoController extends BaseController @Autowired private FileStorageService fileStorageService;//注入实列 + @Autowired + private SftpProperties sftpProperties; + + @Autowired + private ServerConfig serverConfig; /** * 自己新增river_news */ @@ -53,6 +65,7 @@ public class ToutiaoController extends BaseController @RequestParam(value = "remark",required = false) String remark, @RequestParam(value = "littleTitle1",required = false) String littleTitle1, @RequestParam(value = "littleTitle2",required = false) String littleTitle2 + ,@RequestParam(value = "source",required = false) String source ) { Toutiao toutiao = new Toutiao(); @@ -64,16 +77,20 @@ public class ToutiaoController extends BaseController toutiao.setRemark(remark); toutiao.setLittleTitle1(littleTitle1); toutiao.setLittleTitle2(littleTitle2); + toutiao.setSource(source); // 处理文件上传 if (coverPath != null && !coverPath.isEmpty()) { - String basePath = "articleFilePath/"; - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) - + "/" + coverPath.getOriginalFilename(); - - FileInfo fileInfo = fileStorageService.of(coverPath) - .setPath(objectName) - .upload(); - toutiao.setCoverPath(fileInfo.getUrl()); + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, coverPath); + String url = serverConfig.getUrl() + fileName; + toutiao.setCoverPath(url); + } catch (Exception e) { + e.printStackTrace(); + return AjaxResult.error("封面上传失败"); + } } return toAjax(toutiaoService.insertToutiaoOwn(toutiao)); } @@ -93,6 +110,7 @@ public class ToutiaoController extends BaseController @RequestParam(value = "remark",required = false) String remark, @RequestParam(value = "littleTitle1",required = false) String littleTitle1, @RequestParam(value = "littleTitle2",required = false) String littleTitle2 + ,@RequestParam(value = "source",required = false) String source ) { Toutiao toutiao = new Toutiao(); @@ -105,16 +123,21 @@ public class ToutiaoController extends BaseController toutiao.setRemark(remark); toutiao.setLittleTitle1(littleTitle1); toutiao.setLittleTitle2(littleTitle2); + toutiao.setSource(source); // 处理文件上传 if (coverPath != null && !coverPath.isEmpty()) { - String basePath = "articleFilePath/"; - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) - + "/" + coverPath.getOriginalFilename(); - - FileInfo fileInfo = fileStorageService.of(coverPath) - .setPath(objectName) - .upload(); - toutiao.setCoverPath(fileInfo.getUrl()); + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, coverPath); + String url = serverConfig.getUrl() + fileName; + toutiao.setCoverPath(url); + } catch (Exception e) { + e.printStackTrace(); + return AjaxResult.error("封面上传失败"); + } + } return toAjax(toutiaoService.updateToutiaoOwn(toutiao)); } @@ -187,4 +210,87 @@ public class ToutiaoController extends BaseController { return toAjax(toutiaoService.deleteToutiaoByIds(ids)); } + + + /** + * 附件上传 - 上传到远程服务器 + */ + @Anonymous + @PostMapping("/attachment/{id}") + public AjaxResult uploadAttachment(@PathVariable Long id, @RequestParam MultipartFile file) throws Exception { + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } + + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + url=url.replaceAll("http://", "https://"); + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); + ajax.put("originalFilename", file.getOriginalFilename()); + + // 先查询完整的Toutiao对象 + Toutiao toutiao = toutiaoService.getById(id); + if (toutiao != null) { + // 更新attachment字段 + toutiao.setAttachment(url); + // 更新数据库中的attachment字段 + toutiaoService.updateById(toutiao); + } + System.out.println("文章附件:" + url); + + return ajax; + + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + + /* 原OSS上传代码 - 已注释 + try { + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } + + // 获取 base-path + String basePath = "touTioaPath/attachment/"; + + // 指定oss保存文件路径 + String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + file.getOriginalFilename(); + // 上传文件,成功返回文件信息 + FileInfo fileInfo = fileStorageService.of(file).setPath(objectName).upload(); + + // 设置返回结果 + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", fileInfo.getUrl()); + ajax.put("fileName", fileInfo.getUrl()); + ajax.put("newFileName", fileInfo.getUrl()); + ajax.put("originalFilename", file.getOriginalFilename()); + + // 先查询完整的Article对象 + Toutiao toutiao = toutiaoService.getById(id); + if (toutiao != null) { + // 更新attachment字段 + toutiao.setAttachment(fileInfo.getUrl()); + // 更新数据库中的attachment字段 + toutiaoService.updateById(toutiao); + } + System.out.println("文章附件:"+fileInfo.getUrl()); + + return ajax; + + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + */ + } + + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/UncheckDraftController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/UncheckDraftController.java index 51054d4b0b2ba169631f2d077bb64733c54364a9..244459c91a9c98cdb4a199440da2a6f170c14706 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/UncheckDraftController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/UncheckDraftController.java @@ -8,6 +8,7 @@ import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.system.mapper.SysDeptMapper; import org.springframework.security.access.prepost.PreAuthorize; @@ -41,6 +42,8 @@ public class UncheckDraftController extends BaseController @Autowired private SysDeptMapper sysDeptMapper; + @Autowired + private ServerConfig serverConfig; /** * 判断部门是否直接隶属于荆州市长江河道管理局(一级子部门) * @param dept 部门信息 @@ -227,8 +230,7 @@ public class UncheckDraftController extends BaseController // 设置草稿状态 uncheckDraft.setStatus(0); // 0表示草稿状态 - - return toAjax(uncheckDraftService.insertUncheckDraft(uncheckDraft)); + return toAjax(uncheckDraftService.save(uncheckDraft)); } /** @@ -254,7 +256,7 @@ public class UncheckDraftController extends BaseController uncheckDraft.setDeptName(sysUser.getDept().getDeptName()); } - return toAjax(uncheckDraftService.updateUncheckDraft(uncheckDraft)); + return toAjax(uncheckDraftService.updateById(uncheckDraft)); } /** diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/VideoRecoverStationController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/VideoRecoverStationController.java index 1ac5bcae833290fb2e393bfe8cc91c754e8ed994..d1f705aa158c7f0e87b7b61a41f16a2207c3e1d3 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/VideoRecoverStationController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/VideoRecoverStationController.java @@ -2,6 +2,8 @@ package com.ruoyi.controller; import java.util.List; import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.framework.config.ServerConfig; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -34,6 +36,8 @@ public class VideoRecoverStationController extends BaseController @Autowired private IVideoRecoverStationService videoRecoverStationService; + @Autowired + private ServerConfig serverConfig; /** * 查询视频回收站列表 */ @@ -42,6 +46,7 @@ public class VideoRecoverStationController extends BaseController public TableDataInfo list(VideoRecoverStation videoRecoverStation) { startPage(); + videoRecoverStation.setUserId(getUserId()); List list = videoRecoverStationService.selectVideoRecoverStationList(videoRecoverStation); return getDataTable(list); } @@ -77,6 +82,7 @@ public class VideoRecoverStationController extends BaseController @PostMapping public AjaxResult add(@RequestBody VideoRecoverStation videoRecoverStation) { + videoRecoverStation.setUserId(getUserId()); return toAjax(videoRecoverStationService.insertVideoRecoverStation(videoRecoverStation)); } @@ -88,6 +94,7 @@ public class VideoRecoverStationController extends BaseController @PutMapping public AjaxResult edit(@RequestBody VideoRecoverStation videoRecoverStation) { + videoRecoverStation.setUserId(getUserId()); return toAjax(videoRecoverStationService.updateVideoRecoverStation(videoRecoverStation)); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/controller/WaterController.java b/ruoyi-admin/src/main/java/com/ruoyi/controller/WaterController.java index cbd7dd7d2b0b56ca0cafbfca054367583c18ce8c..dd46ec1276712420ee143bf5cdb7afff931b8b72 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/controller/WaterController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/controller/WaterController.java @@ -4,6 +4,7 @@ import java.util.List; import javax.servlet.http.HttpServletResponse; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.framework.config.ServerConfig; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -36,6 +37,8 @@ public class WaterController extends BaseController @Autowired private IWaterService waterService; + @Autowired + private ServerConfig serverConfig; /** * 查询水情管理列表 */ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/domain/Article.java b/ruoyi-admin/src/main/java/com/ruoyi/domain/Article.java index a71a2d7313247111465f20ce74e6fa984715b05f..d0f08ee3251a9f0b9af19080ed526278adcd020b 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/domain/Article.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/domain/Article.java @@ -35,11 +35,14 @@ public class Article extends OurBaseEntity private String title; - /** 文章来源 */ - @Excel(name = "文章来源") @TableField(value = "article_origin") private String articleOrigin; + /** 文章来源 */ + @Excel(name = "文章来源") + @TableField(value = "source") + private String source; + /** 栏目id */ @Excel(name = "栏目id") @TableField(value = "column_id") @@ -108,7 +111,7 @@ public class Article extends OurBaseEntity @TableField(exist = false) private String endPubdate; - @TableField("pdf_image_paths") // 数据库表需同步新增该字段(VARCHAR类型,长度255+) + @TableField("pdf_image_paths") private String pdfImagePaths; diff --git a/ruoyi-admin/src/main/java/com/ruoyi/domain/ArticleUncheck.java b/ruoyi-admin/src/main/java/com/ruoyi/domain/ArticleUncheck.java index bea0fa78f977dcb02d4d1967f43c31c4a0ae86ca..a4eccfc3153384802ff4b16cb8d0a88304b2d257 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/domain/ArticleUncheck.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/domain/ArticleUncheck.java @@ -11,7 +11,7 @@ import com.ruoyi.common.annotation.Excel; /** * uncheck对象 article_uncheck - * + * * @author ya * @date 2025-07-12 */ @@ -29,11 +29,15 @@ public class ArticleUncheck extends OurBaseEntity @TableField(value = "title") private String title; - /** 文章来源 */ - @Excel(name = "文章来源") + @TableField(value = "article_origin") private String articleOrigin; + /** 文章来源 */ + @Excel(name = "文章来源") + @TableField(value = "source") + private String source; + /** 栏目id */ @Excel(name = "栏目id") @TableField(value = "column_id") @@ -100,7 +104,7 @@ public class ArticleUncheck extends OurBaseEntity public void setClearPubdate(boolean clearPubdate) { this.clearPubdate = clearPubdate; } - + /** 开始发布日期 */ @TableField(exist = false) private String beginPubdate; @@ -110,4 +114,5 @@ public class ArticleUncheck extends OurBaseEntity private String endPubdate; + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/domain/ChartColumn.java b/ruoyi-admin/src/main/java/com/ruoyi/domain/ChartColumn.java new file mode 100644 index 0000000000000000000000000000000000000000..c5fbfc297d743ccd3045bd702d4c122ca5f0e627 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/domain/ChartColumn.java @@ -0,0 +1,70 @@ +package com.ruoyi.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 首页图表栏目动态显示对象 chart_column + * + * @author ya + * @date 2025-09-29 + */ +public class ChartColumn extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 图表栏目id */ + private Long id; + + /** 网站栏目名称 */ + @Excel(name = "网站栏目名称") + private String name; + + /** 显示为:0,隐藏:1 */ + @Excel(name = "显示为:0,隐藏:1") + private Long status; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + public void setName(String name) + { + this.name = name; + } + + public String getName() + { + return name; + } + public void setStatus(Long status) + { + this.status = status; + } + + public Long getStatus() + { + return status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("name", getName()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateTime", getUpdateTime()) + .append("updateBy", getUpdateBy()) + .append("remark", getRemark()) + .append("status", getStatus()) + .toString(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/domain/ColumnManagement.java b/ruoyi-admin/src/main/java/com/ruoyi/domain/ColumnManagement.java new file mode 100644 index 0000000000000000000000000000000000000000..6855960bbe829470f48cb435f8ffa81dfea10402 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/domain/ColumnManagement.java @@ -0,0 +1,22 @@ +package com.ruoyi.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +@TableName("column_management") +@Data +public class ColumnManagement implements Serializable { + private static final long serialVersionUID = 1L; + /** 主键ID */ + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + @TableField(value = "name") + private String name; + /** 栏目名称 */ + @TableField(value = "column_name") + private String columnName; +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/domain/DraftArticle.java b/ruoyi-admin/src/main/java/com/ruoyi/domain/DraftArticle.java index 5309b0eb44456a79e8f227e634d4e0948b4f71f4..7f8e8e37b126a5792cc8b156fb511db77374b081 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/domain/DraftArticle.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/domain/DraftArticle.java @@ -113,4 +113,4 @@ public class DraftArticle extends OurBaseEntity -} \ No newline at end of file +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/domain/Partyclass.java b/ruoyi-admin/src/main/java/com/ruoyi/domain/Partyclass.java index 1403f5cd71f200bd53cc08f34ad275b86dd7e3d7..9f714703a02b84129347f393c902fa4f29fa06c9 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/domain/Partyclass.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/domain/Partyclass.java @@ -14,7 +14,7 @@ import java.util.Date; /** * partyclass对象 partyclass - * + * * @author ya * @date 2025-07-11 */ @@ -42,8 +42,8 @@ public class Partyclass implements Serializable @TableField(value = "pubdate") private String pubdate; - /** 责任编辑 */ - @Excel(name = "责任编辑") + /** 视频来源 */ + @Excel(name = "视频来源") @TableField(value = "author") private String author; @@ -55,22 +55,22 @@ public class Partyclass implements Serializable @Excel(name = "视频类型") @TableField(value = "type") private Integer type; - + /** 封面图片路径 */ @Excel(name = "封面图片路径") @TableField(value = "cover_img") private String coverImg; - + /** 坐标x */ @Excel(name = "坐标x") @TableField(value = "x") private Double x; - + /** 坐标y */ @Excel(name = "坐标y") @TableField(value = "y") private Double y; - + /** 排序字段 */ @Excel(name = "排序") @TableField(value = "sort_order") @@ -81,6 +81,8 @@ public class Partyclass implements Serializable @TableField(value = "publish") private Integer publish; + @TableField(value = "user_id") + private Long userId; // @TableField(exist = false) // private Date updateTime; } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/domain/RecoverStation.java b/ruoyi-admin/src/main/java/com/ruoyi/domain/RecoverStation.java index 850933ae77d8333e8e2cb23b4e38cfa3f74101ad..913cebf571753edc8e75554463c458d64ba8d21f 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/domain/RecoverStation.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/domain/RecoverStation.java @@ -15,7 +15,7 @@ import com.ruoyi.common.core.domain.BaseEntity; /** * 回收站对象 recover_station - * + * * @author ya * @date 2025-08-01 */ @@ -34,11 +34,14 @@ public class RecoverStation extends OurBaseEntity @Excel(name = "标题") private String title; - /** 文章来源 */ @TableField(value="article_origin") - @Excel(name = "文章来源") private String articleOrigin; + /** 文章来源 */ + @Excel(name = "文章来源") + @TableField(value = "source") + private String source; + /** 栏目id */ @TableField(value="column_id") @Excel(name = "栏目id") @@ -115,4 +118,7 @@ public class RecoverStation extends OurBaseEntity @TableField(value="attachment") private String attachment; + @TableField("pdf_image_paths") + private String pdfImagePaths; + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/domain/RiverNews.java b/ruoyi-admin/src/main/java/com/ruoyi/domain/RiverNews.java index 5b7b52b56c67251ff2af8d2caf3f0d3f9cf97af4..b76a3fa390b2b49688bad41801f508939d8d09ea 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/domain/RiverNews.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/domain/RiverNews.java @@ -11,7 +11,7 @@ import com.ruoyi.common.core.domain.BaseEntity; /** * river_news对象 river_news - * + * * @author ya * @date 2025-06-22 */ @@ -30,11 +30,14 @@ public class RiverNews extends OurBaseEntity @TableField(value = "title") private String title; - /** 文章来源 */ - @Excel(name = "文章来源") @TableField(value = "article_origin") private String articleOrigin; + /** 文章来源 */ + @Excel(name = "文章来源") + @TableField(value = "source") + private String source; + /** 栏目id */ @Excel(name = "栏目id") @TableField(value = "column_id") diff --git a/ruoyi-admin/src/main/java/com/ruoyi/domain/TextReadDemo.java b/ruoyi-admin/src/main/java/com/ruoyi/domain/TextReadDemo.java new file mode 100644 index 0000000000000000000000000000000000000000..e6c19d042d1fb8091814de84d10877f6940ab755 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/domain/TextReadDemo.java @@ -0,0 +1,20 @@ +package com.ruoyi.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import lombok.Data; + +import java.util.Date; + +@Data +public class TextReadDemo { + private Integer id; + private String name; + /** 封面路径 */ + private String coverPath; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") // 指定格式和时区 + private Date publishTime; +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/domain/Toutiao.java b/ruoyi-admin/src/main/java/com/ruoyi/domain/Toutiao.java index a26574db0f7c033f0008aa9253a9301fc33b2ec8..8eaefa7de80ed9622356006c6bbaa679af733cc9 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/domain/Toutiao.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/domain/Toutiao.java @@ -11,7 +11,7 @@ import com.ruoyi.common.core.domain.BaseEntity; /** * toutiao对象 toutiao - * + * * @author ya * @date 2025-06-23 */ @@ -29,11 +29,14 @@ public class Toutiao extends OurBaseEntity @TableField(value = "title") private String title; - /** 文章来源 */ - @Excel(name = "文章来源") @TableField(value = "article_origin") private String articleOrigin; + /** 文章来源 */ + @Excel(name = "文章来源") + @TableField(value = "source") + private String source; + /** 栏目id */ @Excel(name = "栏目id") @TableField(value = "column_id") diff --git a/ruoyi-admin/src/main/java/com/ruoyi/domain/UncheckDraft.java b/ruoyi-admin/src/main/java/com/ruoyi/domain/UncheckDraft.java index 5dcd7bcf39f6e9ca846457212ac95bf352c4ee3b..cad238e7686346173ac7df17dbd8486349671ce9 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/domain/UncheckDraft.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/domain/UncheckDraft.java @@ -38,10 +38,13 @@ public class UncheckDraft extends OurBaseEntity /** 文章来源 */ - @Excel(name = "文章来源") @TableField(value = "article_origin") private String articleOrigin; + @Excel(name = "文章来源") + @TableField(value = "source") + private String source; + /** 部门名称 */ @Excel(name = "部门名称") @TableField(value = "dept_name") @@ -117,4 +120,4 @@ public class UncheckDraft extends OurBaseEntity @TableField(value="attachment") private String attachment; -} \ No newline at end of file +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/domain/VideoRecoverStation.java b/ruoyi-admin/src/main/java/com/ruoyi/domain/VideoRecoverStation.java index 8af1936ddde72f974f4d6b8b0204fbe1982a188d..31f86eace7beaa785dee756369de8699396b997d 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/domain/VideoRecoverStation.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/domain/VideoRecoverStation.java @@ -14,7 +14,7 @@ import com.ruoyi.common.core.domain.BaseEntity; /** * 视频回收站对象 videoRecoverStation - * + * * @author ruoyi * @date 2025-09-12 */ @@ -38,8 +38,8 @@ public class VideoRecoverStation @TableField(value = "pubdate") private String pubdate; - /** 责任编辑 */ - @Excel(name = "责任编辑") + /** 视频来源 */ + @Excel(name = "视频来源") private String author; /** 责任部门 */ @@ -67,5 +67,7 @@ public class VideoRecoverStation @TableField(value = "valid_date") private int validDate; + @TableField(value = "user_id") + private Long userId; } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/domain/vo/ArticleColumnVo.java b/ruoyi-admin/src/main/java/com/ruoyi/domain/vo/ArticleColumnVo.java index c860213b12dad133362cb29793f49217d5db47c2..442bad99976a3bbe277d72a5b9f91abb75f89657 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/domain/vo/ArticleColumnVo.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/domain/vo/ArticleColumnVo.java @@ -1,8 +1,12 @@ package com.ruoyi.domain.vo; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; +@AllArgsConstructor +@NoArgsConstructor @Data public class ArticleColumnVo { private String columnName; diff --git a/ruoyi-admin/src/main/java/com/ruoyi/mapper/ArticleMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/mapper/ArticleMapper.java index ee9b5c5fba05c8101bc21d05b2ac624b4ef158d3..33d2b8713f571adb1a6e5bd4946ebf5f9fcd82fe 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/mapper/ArticleMapper.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/mapper/ArticleMapper.java @@ -34,15 +34,6 @@ public interface ArticleMapper extends BaseMapper
"ORDER BY count DESC") List selectArticleOriginDateList(@Param("ew") LambdaQueryWrapper
wrapper); - /** - * 根据条件统计各部门及子部门文章数量 - */ - @Select("SELECT #{deptName} AS deptName, COUNT(*) AS count FROM article " + - "WHERE ${ew.customSqlSegment} " + - "AND article_origin IN (${originList})") - DeptArticleCountVo selectDeptArticleCount(@Param("deptName") String deptName, - @Param("ew") LambdaQueryWrapper
wrapper, - @Param("originList") String originList); /** @@ -142,4 +133,6 @@ public interface ArticleMapper extends BaseMapper
* @return 结果 */ public int batchShowArticle(Long[] articleIds); + + public List
selectArticlesByColumnId(Integer columnId); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/mapper/ChartColumnMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/mapper/ChartColumnMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..3ba5bf5e32ddc74d023ee529ce85b9d4a2938bd5 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/mapper/ChartColumnMapper.java @@ -0,0 +1,70 @@ +package com.ruoyi.mapper; + +import java.util.List; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.domain.ChartColumn; + +/** + * 首页图表栏目动态显示Mapper接口 + * + * @author ya + * @date 2025-09-29 + */ +public interface ChartColumnMapper extends BaseMapper +{ + /** + * 查询首页图表栏目动态显示 + * + * @param id 首页图表栏目动态显示主键 + * @return 首页图表栏目动态显示 + */ + public ChartColumn selectChartColumnById(Long id); + + /** + * 查询首页图表栏目动态显示列表 + * + * @param chartColumn 首页图表栏目动态显示 + * @return 首页图表栏目动态显示集合 + */ + public List selectChartColumnList(ChartColumn chartColumn); + + /** + * 新增首页图表栏目动态显示 + * + * @param chartColumn 首页图表栏目动态显示 + * @return 结果 + */ + public int insertChartColumn(ChartColumn chartColumn); + + /** + * 修改首页图表栏目动态显示 + * + * @param chartColumn 首页图表栏目动态显示 + * @return 结果 + */ + public int updateChartColumn(ChartColumn chartColumn); + + /** + * 删除首页图表栏目动态显示 + * + * @param id 首页图表栏目动态显示主键 + * @return 结果 + */ + public int deleteChartColumnById(Long id); + + /** + * 批量删除首页图表栏目动态显示 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteChartColumnByIds(Long[] ids); + + /** + * 查询所有状态为显示的图表栏目列表 + * + * @return 状态为显示的图表栏目集合 + */ + public List selectChartColumnListByStatus(); +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/mapper/ColumnManagementMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/mapper/ColumnManagementMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..bd74e160226511318ada91b8b6dc771feb2e402f --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/mapper/ColumnManagementMapper.java @@ -0,0 +1,9 @@ +package com.ruoyi.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.domain.ColumnManagement; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ColumnManagementMapper extends BaseMapper { +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/mapper/ConlumnMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/mapper/ConlumnMapper.java index b6d7844ef971b7078a411f64c2ce5fc4ec443375..7a313700acadc25c4eecf1773dd12bf8b9253a24 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/mapper/ConlumnMapper.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/mapper/ConlumnMapper.java @@ -35,6 +35,7 @@ public interface ConlumnMapper extends BaseMapper List selectConlumnListByRole(@Param("isAdmin") boolean isAdmin, @Param("deptId") Long deptId, @Param("column") Conlumn column); + /** * 查询栏目管理 * diff --git a/ruoyi-admin/src/main/java/com/ruoyi/mapper/HomePageMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/mapper/HomePageMapper.java index 2f024a507c20b776efb7466d552029465c662455..275a57f13f19063e480b0e4965bde61f75574da9 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/mapper/HomePageMapper.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/mapper/HomePageMapper.java @@ -1,4 +1,4 @@ -package com.ruoyi.system.mapper; +package com.ruoyi.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.domain.HomePage; diff --git a/ruoyi-admin/src/main/java/com/ruoyi/service/ArticleService.java b/ruoyi-admin/src/main/java/com/ruoyi/service/ArticleService.java index 024a5f8d4bc7e8c42829a4b4964ab773d9647a8b..cd6f889c84ecb11ed713d210be950bdebb4a71af 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/service/ArticleService.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/service/ArticleService.java @@ -19,11 +19,6 @@ public interface ArticleService extends IService
{ - /** - * 日期区间查询对应分局的不同栏目文章数量 - * 根据当前登录用户所属的分局来统计该分局在某个时间段内发布的文章对应的不同栏目数量 - */ - public List selectArticleColumnListByDept(String deptName, String startTime, String endTime); /** * 日期区间查询对应部门及其子部门的不同栏目文章数量 @@ -52,7 +47,7 @@ public interface ArticleService extends IService
/** * 按部门统计文章数量 */ - public List selectDeptArticleCountList(String startTime, String endTime); + public List selectDeptArticleCountList(String startTime, String endTime, List columnIds); /** @@ -152,4 +147,6 @@ public interface ArticleService extends IService
* @return 结果 */ public int batchShowArticle(Long[] articleIds); + + public List
selectArticleListByColumnId(Integer columnId); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/service/ColumnManagementService.java b/ruoyi-admin/src/main/java/com/ruoyi/service/ColumnManagementService.java new file mode 100644 index 0000000000000000000000000000000000000000..f2829062cfac5bd883653f38dcac9c6ae612411c --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/service/ColumnManagementService.java @@ -0,0 +1,8 @@ +package com.ruoyi.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.ruoyi.domain.ColumnManagement; + +public interface ColumnManagementService extends IService { + public ColumnManagement getByName(String name); +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/service/IChartColumnService.java b/ruoyi-admin/src/main/java/com/ruoyi/service/IChartColumnService.java new file mode 100644 index 0000000000000000000000000000000000000000..6dfb30296d82a9f8775f582430c390f9f865a73e --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/service/IChartColumnService.java @@ -0,0 +1,70 @@ +package com.ruoyi.service; + +import java.util.List; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.ruoyi.domain.ChartColumn; + +/** + * 首页图表栏目动态显示Service接口 + * + * @author ya + * @date 2025-09-29 + */ +public interface IChartColumnService extends IService +{ + /** + * 查询首页图表栏目动态显示 + * + * @param id 首页图表栏目动态显示主键 + * @return 首页图表栏目动态显示 + */ + public ChartColumn selectChartColumnById(Long id); + + /** + * 查询首页图表栏目动态显示列表 + * + * @param chartColumn 首页图表栏目动态显示 + * @return 首页图表栏目动态显示集合 + */ + public List selectChartColumnList(ChartColumn chartColumn); + + /** + * 新增首页图表栏目动态显示 + * + * @param chartColumn 首页图表栏目动态显示 + * @return 结果 + */ + public int insertChartColumn(ChartColumn chartColumn); + + /** + * 修改首页图表栏目动态显示 + * + * @param chartColumn 首页图表栏目动态显示 + * @return 结果 + */ + public int updateChartColumn(ChartColumn chartColumn); + + /** + * 批量删除首页图表栏目动态显示 + * + * @param ids 需要删除的首页图表栏目动态显示主键集合 + * @return 结果 + */ + public int deleteChartColumnByIds(Long[] ids); + + /** + * 删除首页图表栏目动态显示信息 + * + * @param id 首页图表栏目动态显示主键 + * @return 结果 + */ + public int deleteChartColumnById(Long id); + + /** + * 查询所有状态为显示的图表栏目列表 + * + * @return 状态为显示的图表栏目集合 + */ + public List selectChartColumnListByStatus(); +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/service/IPartyclassService.java b/ruoyi-admin/src/main/java/com/ruoyi/service/IPartyclassService.java index c27f568d97868ade9c5479f322aa796ec30a8c49..0d667e3236325b57f8f690d6d3f78557417eeebb 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/service/IPartyclassService.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/service/IPartyclassService.java @@ -14,6 +14,9 @@ import com.ruoyi.domain.Partyclass; public interface IPartyclassService extends IService { + // 在 IPartyclassService 接口中添加 + void decrementSortOrderForTypeGreaterThan(int type, int sortOrder); + /** * 视频类型查询视频列表 */ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ArticleServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ArticleServiceImpl.java index c90f1fed8a356e30c1a58f522862911550d8a280..34414529437c577f6ee64ea4d4a1d54e017cac07 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ArticleServiceImpl.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ArticleServiceImpl.java @@ -6,15 +6,11 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; -import com.ruoyi.domain.Article; -import com.ruoyi.domain.Conlumn; -import com.ruoyi.domain.RecoverStation; +import com.ruoyi.domain.*; import com.ruoyi.domain.vo.ArticleColumnVo; import com.ruoyi.domain.vo.ArticleOriginDateVo; import com.ruoyi.domain.vo.DeptArticleCountVo; -import com.ruoyi.mapper.ArticleMapper; -import com.ruoyi.mapper.ConlumnMapper; -import com.ruoyi.mapper.RecoverStationMapper; +import com.ruoyi.mapper.*; import com.ruoyi.service.ArticleService; import com.ruoyi.system.mapper.SysDeptMapper; import org.springframework.beans.factory.annotation.Autowired; @@ -22,6 +18,9 @@ import org.springframework.stereotype.Service; import java.util.*; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; + /** * articleService业务层处理 * @@ -31,12 +30,25 @@ import java.util.*; public class ArticleServiceImpl extends ServiceImpl implements ArticleService { @Autowired private ArticleMapper articleMapper; + + @Autowired + private FileStorageService fileStorageService; + + @Autowired + private PdfParseTaskServiceImpl pdfParseTaskService; + @Autowired private ConlumnMapper conlumnMapper; @Autowired private RecoverStationMapper recoverStationMapper; + @Autowired + private ChartColumnMapper chartColumnMapper; + + @Autowired + private ArticleUncheckMapper articleUncheckMapper; + /** * 判断部门是否直接隶属于荆州市长江河道管理局(一级子部门) @@ -96,117 +108,37 @@ public class ArticleServiceImpl extends ServiceImpl impl columnMap.put(column.getId(), column); } - // 初始化所有栏目计数为0 - Map columnCountMap = new HashMap<>(); - for (Conlumn column : allColumns) { - columnCountMap.put(column.getName(), 0); - } - - // 统计各栏目文章数量(不限制栏目权限) - int nullColumnCount = 0; // 用于统计栏目为null的文章数量 - for (Article article : articles) { - Long columnId = article.getColumnId(); - if (columnId != null) { - Conlumn column = columnMap.get(columnId); - if (column != null) { - String columnName = column.getName(); - columnCountMap.put(columnName, columnCountMap.getOrDefault(columnName, 0) + 1); - } - } else { - // 栏目为null的文章数量统计 - nullColumnCount++; - } - System.out.println("未知栏目数量:"+nullColumnCount); - } - - // 如果有栏目为null的文章,添加到结果中 - if (nullColumnCount > 0) { - columnCountMap.put("未知栏目", nullColumnCount); - } - - // 转换为返回格式 - List result = new ArrayList<>(); - for (Map.Entry entry : columnCountMap.entrySet()) { - ArticleColumnVo vo = new ArticleColumnVo(); - vo.setColumnName(entry.getKey()); - vo.setCount(entry.getValue()); - result.add(vo); - } - - // 按文章数量排序 - result.sort((a, b) -> b.getCount()); - - System.out.println("最终结果: " + result); - - return result; - } - - @Override - public List selectArticleColumnListByDept(String deptName, String startTime, String endTime) { - // 如果没有指定时间范围,默认查询本年1月1日到当前日期 - if (StringUtils.isEmpty(startTime) && StringUtils.isEmpty(endTime)) { - String yearStart = DateUtils.dateTimeNow("yyyy") + "-01-01"; - String today = DateUtils.dateTimeNow("yyyy-MM-dd"); - startTime = yearStart; - endTime = today; - } - - System.out.println("输入参数: deptName=" + deptName + ", startTime=" + startTime + ", endTime=" + endTime); - - // 直接根据部门名称(文章来源)查找文章 - LambdaQueryWrapper
wrapper = new LambdaQueryWrapper<>(); - wrapper.select(Article::getColumnId, Article::getArticleOrigin); // 只选择需要的字段 - if (StringUtils.isNotEmpty(deptName)) { - // 只查询指定部门(分局)的文章 - wrapper.eq(Article::getArticleOrigin, deptName); - } - wrapper.ge(Article::getPubdate, startTime); - wrapper.le(Article::getPubdate, endTime); - wrapper.isNotNull(Article::getArticleOrigin); - wrapper.ne(Article::getArticleOrigin, ""); - - System.out.println("查询条件: " + wrapper.getSqlSegment()); - List
articles = articleMapper.selectList(wrapper); - System.out.println("查询结果数量: " + articles.size()); - - if (articles.isEmpty()) { - System.out.println("未查询到数据,返回空列表"); - return new ArrayList<>(); - } - - // 获取所有栏目信息 - List allColumns = conlumnMapper.selectList(null); - Map columnMap = new HashMap<>(); - for (Conlumn column : allColumns) { - columnMap.put(column.getId(), column); + // 获取显示状态的图表栏目列表 + List displayChartColumns = chartColumnMapper.selectChartColumnListByStatus(); + Set displayColumnNames = new HashSet<>(); + for (ChartColumn chartColumn : displayChartColumns) { + displayColumnNames.add(chartColumn.getName()); } - // 初始化所有栏目计数为0 + // 初始化显示状态的栏目计数为0 Map columnCountMap = new HashMap<>(); for (Conlumn column : allColumns) { - columnCountMap.put(column.getName(), 0); + // 只初始化显示状态的栏目 + if (displayColumnNames.contains(column.getName())) { + columnCountMap.put(column.getName(), 0); + } } - // 统计各栏目文章数量(不限制栏目权限) - int classifiedCount = 0; // 用于统计已分类的文章数量 + // 统计各显示栏目文章数量(不限制栏目权限) for (Article article : articles) { Long columnId = article.getColumnId(); if (columnId != null) { Conlumn column = columnMap.get(columnId); if (column != null) { String columnName = column.getName(); - columnCountMap.put(columnName, columnCountMap.getOrDefault(columnName, 0) + 1); - classifiedCount++; // 增加已分类文章计数 + // 只统计显示状态的栏目 + if (displayColumnNames.contains(columnName)) { + columnCountMap.put(columnName, columnCountMap.getOrDefault(columnName, 0) + 1); + } } } } - // 计算未知栏目的文章数量(总数量 - 已分类数量) - int nullColumnCount = articles.size() - classifiedCount; - if (nullColumnCount > 0) { - columnCountMap.put("未知栏目", nullColumnCount); - } - // 转换为返回格式 List result = new ArrayList<>(); for (Map.Entry entry : columnCountMap.entrySet()) { @@ -223,7 +155,8 @@ public class ArticleServiceImpl extends ServiceImpl impl return result; } - + + @Autowired private SysDeptMapper sysDeptMapper; @@ -300,32 +233,37 @@ public class ArticleServiceImpl extends ServiceImpl impl columnMap.put(column.getId(), column); } - // 初始化所有栏目计数为0 + // 获取显示状态的图表栏目列表 + List displayChartColumns = chartColumnMapper.selectChartColumnListByStatus(); + Set displayColumnNames = new HashSet<>(); + for (ChartColumn chartColumn : displayChartColumns) { + displayColumnNames.add(chartColumn.getName()); + } + + // 初始化显示状态的栏目计数为0 Map columnCountMap = new HashMap<>(); for (Conlumn column : allColumns) { - columnCountMap.put(column.getName(), 0); + // 只初始化显示状态的栏目 + if (displayColumnNames.contains(column.getName())) { + columnCountMap.put(column.getName(), 0); + } } - // 统计各栏目文章数量(不限制栏目权限) - int classifiedCount = 0; // 用于统计已分类的文章数量 + // 统计各显示栏目文章数量(不限制栏目权限) for (Article article : articles) { Long columnId = article.getColumnId(); if (columnId != null) { Conlumn column = columnMap.get(columnId); if (column != null) { String columnName = column.getName(); - columnCountMap.put(columnName, columnCountMap.getOrDefault(columnName, 0) + 1); - classifiedCount++; // 增加已分类文章计数 + // 只统计显示状态的栏目 + if (displayColumnNames.contains(columnName)) { + columnCountMap.put(columnName, columnCountMap.getOrDefault(columnName, 0) + 1); + } } } } - // 计算未知栏目的文章数量(总数量 - 已分类数量) - int nullColumnCount = articles.size() - classifiedCount; - if (nullColumnCount > 0) { - columnCountMap.put("未知栏目", nullColumnCount); - } - // 转换为返回格式 List result = new ArrayList<>(); for (Map.Entry entry : columnCountMap.entrySet()) { @@ -433,15 +371,33 @@ public class ArticleServiceImpl extends ServiceImpl impl columnMap.put(column.getId(), column); } - // 统计各栏目文章数量(不限制栏目权限) + // 获取显示状态的图表栏目列表 + List displayChartColumns = chartColumnMapper.selectChartColumnListByStatus(); + Set displayColumnNames = new HashSet<>(); + for (ChartColumn chartColumn : displayChartColumns) { + displayColumnNames.add(chartColumn.getName()); + } + + // 初始化显示状态的栏目计数为0 Map columnCountMap = new HashMap<>(); + for (Conlumn column : allColumns) { + // 只初始化显示状态的栏目 + if (displayColumnNames.contains(column.getName())) { + columnCountMap.put(column.getName(), 0); + } + } + + // 统计各显示栏目文章数量(不限制栏目权限) for (Article article : allArticles) { Long columnId = article.getColumnId(); if (columnId != null) { Conlumn column = columnMap.get(columnId); if (column != null) { String columnName = column.getName(); - columnCountMap.put(columnName, columnCountMap.getOrDefault(columnName, 0) + 1); + // 只统计显示状态的栏目 + if (displayColumnNames.contains(columnName)) { + columnCountMap.put(columnName, columnCountMap.getOrDefault(columnName, 0) + 1); + } } } } @@ -490,7 +446,7 @@ public class ArticleServiceImpl extends ServiceImpl impl // 按部门统计文章数量 @Override - public List selectDeptArticleCountList(String startTime, String endTime) { + public List selectDeptArticleCountList(String startTime, String endTime, List columnIds) { // 如果没有指定时间范围,默认查询本年1月1日到当前日期 if (StringUtils.isEmpty(startTime) && StringUtils.isEmpty(endTime)) { String yearStart = DateUtils.dateTimeNow("yyyy") + "-01-01"; @@ -499,6 +455,20 @@ public class ArticleServiceImpl extends ServiceImpl impl endTime = today; } + // 获取所有栏目信息 + List allColumns = conlumnMapper.selectList(null); + Map columnMap = new HashMap<>(); + for (Conlumn column : allColumns) { + columnMap.put(column.getId(), column); + } + + // 获取显示状态的图表栏目列表 + List displayChartColumns = chartColumnMapper.selectChartColumnListByStatus(); + Set displayColumnNames = new HashSet<>(); + for (ChartColumn chartColumn : displayChartColumns) { + displayColumnNames.add(chartColumn.getName()); + } + // 获取荆州市长江河道管理局的下一级部门列表(市局/总局和各分局) List firstLevelDepts = getFirstLevelDepts(); System.out.println("部门列表:"+firstLevelDepts); @@ -519,6 +489,21 @@ public class ArticleServiceImpl extends ServiceImpl impl wrapper.le(StringUtils.isNotEmpty(endTime), Article::getPubdate, endTime); wrapper.in(Article::getArticleOrigin, deptNames); + // 只统计显示状态的栏目 + List displayColumnIds = new ArrayList<>(); + for (Conlumn column : allColumns) { + if (displayColumnNames.contains(column.getName())) { + displayColumnIds.add(column.getId()); + } + } + + if (!displayColumnIds.isEmpty()) { + wrapper.in(Article::getColumnId, displayColumnIds); + } else { + // 如果没有显示的栏目,添加一个永远不匹配的条件 + wrapper.eq(Article::getArticleId, -1L); + } + // 统计文章数量 int count = Math.toIntExact(articleMapper.selectCount(wrapper)); @@ -610,6 +595,11 @@ public class ArticleServiceImpl extends ServiceImpl impl recoverStation.setStatus(article.getStatus()); if (article.getAttachment()!= null) recoverStation.setAttachment(article.getAttachment()); + //pdf图片路径 + if (article.getPdfImagePaths()!= null) + recoverStation.setPdfImagePaths(article.getPdfImagePaths()); + if (article.getSource()!= null) + recoverStation.setSource(article.getSource()); // 设置默认有效期为30天 recoverStation.setValidDate(30); recoverStation.setType(0L);//类型为已审核的文章数据回收 @@ -648,38 +638,165 @@ public class ArticleServiceImpl extends ServiceImpl impl } /** - * 新增article + * 新增article - 新逻辑:插入到article_uncheck表(未审核状态) * * @param article article * @return 结果 */ @Override public int insertArticle(Article article, String articleName) { + // 获取栏目ID + LambdaQueryWrapper lambdaQueryWrapper=new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(Conlumn::getName,articleName); + Conlumn conlumn = conlumnMapper.selectOne(lambdaQueryWrapper); + if(null!=conlumn|| StringUtils.isNull(conlumn)){ + article.setColumnId(conlumn.getId()); + } + + // 将Article转换为ArticleUncheck + ArticleUncheck uncheckArticle = new ArticleUncheck(); + uncheckArticle.setTitle(article.getTitle()); + uncheckArticle.setArticleOrigin(article.getArticleOrigin()); + uncheckArticle.setColumnId(article.getColumnId()); + uncheckArticle.setContent(article.getContent()); + uncheckArticle.setAuthor(article.getAuthor()); + uncheckArticle.setCoverPath(article.getCoverPath()); + uncheckArticle.setIp(article.getIp()); + uncheckArticle.setAppval(article.getAppval()); + uncheckArticle.setLittleTitle1(article.getLittleTitle1()); + uncheckArticle.setLittleTitle2(article.getLittleTitle2()); + uncheckArticle.setAttachment(article.getAttachment()); + uncheckArticle.setState("0"); // 未审核状态 + uncheckArticle.setCreateTime(DateUtils.getNowDate()); + uncheckArticle.setCreateBy(article.getCreateBy()); + uncheckArticle.setUpdateTime(DateUtils.getNowDate()); + uncheckArticle.setUpdateBy(article.getUpdateBy()); + uncheckArticle.setRemark(article.getRemark()); + uncheckArticle.setSource(article.getSource()); + // 不设置发布日期,发布日期应该在审核时设置 + + // 插入到article_uncheck表中(不是article表) + int result = articleUncheckMapper.insertArticleUncheck(uncheckArticle); + + System.out.println("【新增文章】已添加到article_uncheck表,文章ID: " + uncheckArticle.getArticleId() + + ", 状态: 未审核"); + + return result; + + /* 原直接插入到article表的代码 - 已注释 article.setCreateTime(DateUtils.getNowDate()); + LambdaQueryWrapper lambdaQueryWrapper=new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(Conlumn::getName,articleName); Conlumn conlumn = conlumnMapper.selectOne(lambdaQueryWrapper); if(null!=conlumn|| StringUtils.isNull(conlumn)){ article.setColumnId(conlumn.getId()); } - return articleMapper.insertArticle(article); + + // 检查是否需要清除PDF图片预览路径 + if (article.getAttachment() == null || article.getAttachment().isEmpty()) { + article.setPdfImagePaths(null); + } + + // 先插入文章 + int result = articleMapper.insertArticle(article); + + // 如果文章有PDF附件,异步解析PDF为图片 + if (result > 0 && article.getAttachment() != null && !article.getAttachment().isEmpty()) { + // 检查附件是否为PDF文件 + if (article.getAttachment().toLowerCase().endsWith(".pdf")) { + // 异步处理PDF解析任务 + pdfParseTaskService.processPdfParseTask(article.getArticleId()); + } + } + + return result; + */ } /** - * 修改article + * 修改article - 新逻辑:删除article,更新对应的uncheck为未审核状态 * * @param article article * @return 结果 */ @Override public int updateArticle(Article article, String articleName) { + // 1. 获取栏目ID LambdaQueryWrapper lambdaQueryWrapper=new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(Conlumn::getName,articleName); Conlumn conlumn = conlumnMapper.selectOne(lambdaQueryWrapper); if(null!=conlumn|| StringUtils.isNull(conlumn)){ article.setColumnId(conlumn.getId()); } - return articleMapper.updateArticle(article); + + // 2. 查询当前article,获取auditId + Article currentArticle = articleMapper.selectArticleByArticleId(article.getArticleId()); + if (currentArticle == null) { + throw new RuntimeException("文章不存在"); + } + + Long auditId = currentArticle.getAuditId(); + if (auditId == null) { + throw new RuntimeException("无法找到对应的待审核文章"); + } + + // 3. 查询对应的uncheck文章 + ArticleUncheck uncheckArticle = articleUncheckMapper.selectArticleUncheckByArticleId(auditId); + if (uncheckArticle == null) { + throw new RuntimeException("对应的待审核文章不存在"); + } + + // 4. 更新uncheck文章内容 + uncheckArticle.setTitle(article.getTitle()); + uncheckArticle.setArticleOrigin(article.getArticleOrigin()); + uncheckArticle.setColumnId(article.getColumnId()); + uncheckArticle.setContent(article.getContent()); + uncheckArticle.setAuthor(article.getAuthor()); + uncheckArticle.setCoverPath(article.getCoverPath()); + uncheckArticle.setIp(article.getIp()); + uncheckArticle.setAppval(article.getAppval()); + uncheckArticle.setLittleTitle1(article.getLittleTitle1()); + uncheckArticle.setLittleTitle2(article.getLittleTitle2()); + uncheckArticle.setAttachment(article.getAttachment()); + uncheckArticle.setSource(article.getSource()); + + + // 5. 将uncheck文章状态改为未审核 + uncheckArticle.setState("0"); + uncheckArticle.setClearPubdate(true); // 清除审核日期 + + // 6. 更新uncheck文章 + int updateResult = articleUncheckMapper.updateArticleUncheck(uncheckArticle); + + // 7. 删除article中的文章 + articleMapper.deleteArticleByArticleId(article.getArticleId()); + + System.out.println("【修改已审核文章】已删除article文章ID: " + article.getArticleId() + + ", 对应uncheck文章ID: " + auditId + " 已改为未审核状态"); + + return updateResult; + + /* 原直接更新article的代码 - 已注释 + // 检查是否需要清除PDF图片预览路径 + if (article.getAttachment() == null || article.getAttachment().isEmpty()) { + article.setPdfImagePaths(null); + } + + // 更新文章 + int result = articleMapper.updateArticle(article); + + // 如果文章有PDF附件,异步解析PDF为图片 + if (result > 0 && article.getAttachment() != null && !article.getAttachment().isEmpty()) { + // 检查附件是否为PDF文件 + if (article.getAttachment().toLowerCase().endsWith(".pdf")) { + // 异步处理PDF解析任务 + pdfParseTaskService.processPdfParseTask(article.getArticleId()); + } + } + + return result; + */ } /** @@ -758,4 +875,9 @@ public class ArticleServiceImpl extends ServiceImpl impl public int batchShowArticle(Long[] articleIds) { return articleMapper.batchShowArticle(articleIds); } + + @Override + public List
selectArticleListByColumnId(Integer columnId) { + return articleMapper.selectArticlesByColumnId(columnId); + } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ArticleUncheckServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ArticleUncheckServiceImpl.java index 9c839c37902c3e739b95b4c46166e795ccfecb5c..cb6ef0d1ab0cb1e8540ca40e219c5d4ade32116b 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ArticleUncheckServiceImpl.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ArticleUncheckServiceImpl.java @@ -4,10 +4,12 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.domain.Article; import com.ruoyi.domain.RecoverStation; +import com.ruoyi.mapper.ArticleMapper; import com.ruoyi.mapper.RecoverStationMapper; import com.ruoyi.system.mapper.SysDeptMapper; import org.springframework.beans.factory.annotation.Autowired; @@ -32,6 +34,8 @@ public class ArticleUncheckServiceImpl extends ServiceImpl logicDeleteUncheck(Long[] articleIds) { @@ -85,10 +89,18 @@ public class ArticleUncheckServiceImpl extends ServiceImpl queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(Article::getTitle, articleUncheck.getTitle()); + // 删除对应的已审核文章 + articleMapper.delete(queryWrapper); + } // 设置默认有效期为30天 recoverStation.setValidDate(30); recoverStation.setType(1L);//回收类型为uncheck diff --git a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ChartColumnServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ChartColumnServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..0bbe15c3d268db1dd82f78da8a1e4fe30fc1baab --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ChartColumnServiceImpl.java @@ -0,0 +1,110 @@ +package com.ruoyi.service.impl; + +import java.util.List; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.ruoyi.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.mapper.ChartColumnMapper; +import com.ruoyi.domain.ChartColumn; +import com.ruoyi.service.IChartColumnService; + +/** + * 首页图表栏目动态显示Service业务层处理 + * + * @author ya + * @date 2025-09-29 + */ +@Service +public class ChartColumnServiceImpl extends ServiceImpl implements IChartColumnService +{ + @Autowired + private ChartColumnMapper chartColumnMapper; + + /** + * 查询首页图表栏目动态显示 + * + * @param id 首页图表栏目动态显示主键 + * @return 首页图表栏目动态显示 + */ + @Override + public ChartColumn selectChartColumnById(Long id) + { + return chartColumnMapper.selectChartColumnById(id); + } + + /** + * 查询首页图表栏目动态显示列表 + * + * @param chartColumn 首页图表栏目动态显示 + * @return 首页图表栏目动态显示 + */ + @Override + public List selectChartColumnList(ChartColumn chartColumn) + { + return chartColumnMapper.selectChartColumnList(chartColumn); + } + + /** + * 新增首页图表栏目动态显示 + * + * @param chartColumn 首页图表栏目动态显示 + * @return 结果 + */ + @Override + public int insertChartColumn(ChartColumn chartColumn) + { + chartColumn.setCreateTime(DateUtils.getNowDate()); + return chartColumnMapper.insertChartColumn(chartColumn); + } + + /** + * 修改首页图表栏目动态显示 + * + * @param chartColumn 首页图表栏目动态显示 + * @return 结果 + */ + @Override + public int updateChartColumn(ChartColumn chartColumn) + { + chartColumn.setUpdateTime(DateUtils.getNowDate()); + return chartColumnMapper.updateChartColumn(chartColumn); + } + + /** + * 批量删除首页图表栏目动态显示 + * + * @param ids 需要删除的首页图表栏目动态显示主键 + * @return 结果 + */ + @Override + public int deleteChartColumnByIds(Long[] ids) + { + return chartColumnMapper.deleteChartColumnByIds(ids); + } + + /** + * 删除首页图表栏目动态显示信息 + * + * @param id 首页图表栏目动态显示主键 + * @return 结果 + */ + @Override + public int deleteChartColumnById(Long id) + { + return chartColumnMapper.deleteChartColumnById(id); + } + + /** + * 查询所有状态为显示的图表栏目列表 + * + * @return 状态为显示的图表栏目集合 + */ + @Override + public List selectChartColumnListByStatus() + { + return chartColumnMapper.selectChartColumnListByStatus(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ColumnManagementImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ColumnManagementImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..f994c76dbe0f85f5ca6a185f70dd024f253de479 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/ColumnManagementImpl.java @@ -0,0 +1,21 @@ +package com.ruoyi.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.ruoyi.domain.ColumnManagement; +import com.ruoyi.mapper.ColumnManagementMapper; +import com.ruoyi.service.ColumnManagementService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class ColumnManagementImpl extends ServiceImpl implements ColumnManagementService { + @Autowired + private ColumnManagementMapper columnManagementMapper; + @Override + public ColumnManagement getByName(String name) { + LambdaQueryWrapper lambdaQueryWrapper=new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ColumnManagement::getName,name); + return columnManagementMapper.selectOne(lambdaQueryWrapper); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/DangerService.java b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/DangerService.java index a01ca3551fdfeb17b60048c75807f238a465ddc0..b7068d8e609dfa9368b0a9427b4096ed1745fbf2 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/DangerService.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/DangerService.java @@ -2,13 +2,19 @@ package com.ruoyi.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.domain.Article; import com.ruoyi.domain.Danger; +import com.ruoyi.framework.config.ServerConfig; import com.ruoyi.mapper.ArticleMapper; import com.ruoyi.mapper.DangerMapper; import com.ruoyi.service.ArticleService; import com.ruoyi.service.IDangerService; +import com.ruoyi.util.FileUploadUtil; +import com.ruoyi.util.SftpProperties; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +34,11 @@ public class DangerService extends ServiceImpl implements @Autowired private FileStorageService fileStorageService;//注入实列 + @Autowired + private SftpProperties sftpProperties; + + @Autowired + private ServerConfig serverConfig; @Override public List selectAll() { return dangerMapper.selectList(null); @@ -39,7 +50,43 @@ public class DangerService extends ServiceImpl implements return dangerMapper.selectOne(lambdaQueryWrapper); } + /** + * 文件上传 - 上传到远程服务器 + */ public AjaxResult uploadFile(int id, MultipartFile file) throws Exception { + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("上传文件为空"); + } + + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); + ajax.put("originalFilename", file.getOriginalFilename()); + + // 创建Danger对象并设置ID和video + Danger danger = new Danger(); + danger.setId(id); + danger.setVideo(url); + + // 插入数据库 + Integer result = dangerMapper.insert(danger); + if (result<=0) { + return AjaxResult.error("插入文件路径失败"); + } + return ajax; + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + + /* 原OSS上传代码 - 已注释 try { // 检查文件是否为空 if (file.isEmpty()) { @@ -78,6 +125,7 @@ public class DangerService extends ServiceImpl implements } catch (Exception e) { return AjaxResult.error(e.getMessage()); } + */ } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/DraftArticleServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/DraftArticleServiceImpl.java index 02901a4f6e24c1ec304d53c61d37025f45d25f10..6873beeab3ff67c12d34c4a718000a7e5da2dbb3 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/DraftArticleServiceImpl.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/DraftArticleServiceImpl.java @@ -1,19 +1,33 @@ package com.ruoyi.service.impl; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.UUID; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.ruoyi.mapper.DraftArticleMapper; import com.ruoyi.mapper.ArticleMapper; +import com.ruoyi.mapper.ArticleUncheckMapper; import com.ruoyi.mapper.ConlumnMapper; import com.ruoyi.domain.DraftArticle; import com.ruoyi.domain.Article; +import com.ruoyi.domain.ArticleUncheck; import com.ruoyi.domain.Conlumn; import com.ruoyi.service.IDraftArticleService; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; /** * 草稿文章Service业务层处理 @@ -30,9 +44,19 @@ public class DraftArticleServiceImpl extends ServiceImpl 0) { + // 删除草稿文章 + draftArticleMapper.deleteDraftArticleByArticleId(articleId); + } + + return result; + } + + /** + * 插入新的uncheck文章 + */ + private int insertNewUncheckArticle(DraftArticle draftArticle) { + ArticleUncheck article = new ArticleUncheck(); + article.setTitle(draftArticle.getTitle()); + article.setArticleOrigin(draftArticle.getArticleOrigin()); + article.setColumnId(draftArticle.getColumnId()); + article.setContent(draftArticle.getContent()); + article.setAuthor(draftArticle.getAuthor()); + article.setCoverPath(draftArticle.getCoverPath()); + article.setIp(draftArticle.getIp()); + article.setAppval(draftArticle.getAppval()); + article.setLittleTitle1(draftArticle.getLittleTitle1()); + article.setLittleTitle2(draftArticle.getLittleTitle2()); + article.setState("0"); // 未审核状态 + article.setCreateTime(draftArticle.getCreateTime()); + article.setCreateBy(draftArticle.getCreateBy()); + article.setUpdateTime(new Date()); + article.setUpdateBy(draftArticle.getUpdateBy()); + article.setRemark(draftArticle.getRemark()); + article.setAttachment(draftArticle.getAttachment()); + // 不设置发布日期,发布日期应该在审核时设置 + + int result = articleUncheckMapper.insertArticleUncheck(article); + + System.out.println("【发布草稿文章】插入article_uncheck表,文章ID: " + article.getArticleId() + + ", 状态: 未审核"); + + return result; + } + + /* 原直接发布到article表的代码 - 已注释 // 将DraftArticle转换为Article Article article = new Article(); article.setTitle(draftArticle.getTitle()); @@ -180,10 +284,20 @@ public class DraftArticleServiceImpl extends ServiceImpl 0 && draftArticle.getAttachment() != null && !draftArticle.getAttachment().isEmpty()) { + // 检查附件是否为PDF文件 + if (draftArticle.getAttachment().toLowerCase().endsWith(".pdf")) { + // 异步处理PDF解析任务 + pdfParseTaskService.processPdfParseTask(article.getArticleId()); + } + } + if (result > 0) { // 删除草稿文章 draftArticleMapper.deleteDraftArticleByArticleId(articleId); } return result; - } + */ } \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/HomePageServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/HomePageServiceImpl.java index 3d3b8a7fca81ba12837f8c815a32a44481d41ea1..dbecbcf82f359f67903df4e2541522c54e95db48 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/HomePageServiceImpl.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/HomePageServiceImpl.java @@ -8,7 +8,7 @@ import com.ruoyi.domain.HomePage; import com.ruoyi.service.IHomePageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import com.ruoyi.system.mapper.HomePageMapper; +import com.ruoyi.mapper.HomePageMapper; /** * 首页轮播图Service业务层处理 diff --git a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/PartyclassServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/PartyclassServiceImpl.java index a0236bdc41de55c7feee598935361f2c5b32b7b2..e161da673e5f1ae9e365ab742a012f9806d8b32e 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/PartyclassServiceImpl.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/PartyclassServiceImpl.java @@ -2,6 +2,7 @@ package com.ruoyi.service.impl; import java.util.List; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.mapper.RecoverStationMapper; import org.springframework.beans.factory.annotation.Autowired; @@ -29,6 +30,23 @@ public class PartyclassServiceImpl extends ServiceImpl queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(Partyclass::getType, type) + .gt(Partyclass::getSortOrder, sortOrder); + List partyclassList = this.list(queryWrapper); + + for (Partyclass partyclass : partyclassList) { + partyclass.setSortOrder(partyclass.getSortOrder() - 1); + this.updateById(partyclass); + } + } + + @Override public List selectPartyclassByType(int type) { return partyclassMapper.selectPartyclassByType(type); @@ -107,6 +125,7 @@ public class PartyclassServiceImpl extends ServiceImpl imageUrls = new ArrayList<>(); + PDDocument document = null; + try { - // 1. PDF转图片(处理所有页) - org.apache.pdfbox.pdmodel.PDDocument document = org.apache.pdfbox.pdmodel.PDDocument.load(pdfInputStream); + // 1. 检查输入流 + if (pdfInputStream == null) { + System.err.println("PDF输入流为null"); + return; + } + + // 2. 加载PDF文档 + document = PDDocument.load(pdfInputStream); int totalPages = document.getNumberOfPages(); + System.out.println("PDF总页数: " + totalPages); + if (totalPages == 0) { - document.close(); + System.out.println("PDF文档没有页面"); return; } - org.apache.pdfbox.rendering.PDFRenderer renderer = new org.apache.pdfbox.rendering.PDFRenderer(document); - List imageUrls = new ArrayList<>(); // 存储所有页图片URL - String basePath = "article/pdf-images/"; // OSS基础路径 - String uuid = com.ruoyi.common.utils.uuid.UUID.randomUUID().toString(); // 生成唯一ID用于关联同PDF的多页图片 + PDFRenderer renderer = new PDFRenderer(document); + String uuid = UUID.randomUUID().toString(); + System.out.println("生成唯一标识: " + uuid); + + // 3. 使用固定服务器URL(避免依赖HttpServletRequest) + String serverUrl = FIXED_SERVER_URL; + System.out.println("使用固定服务器URL: " + serverUrl); + + // 确保服务器URL以斜杠结尾 + if (!serverUrl.endsWith("/")) { + serverUrl += "/"; + } + + // 4. 获取上传路径并检查 + String filePath = RuoYiConfig.getUploadPath(); + System.out.println("上传文件路径: " + filePath); + + if (filePath == null || filePath.isEmpty()) { + System.err.println("上传路径配置为空"); + return; + } - // 循环处理每一页 + // 5. 循环处理每一页 for (int pageNum = 0; pageNum < totalPages; pageNum++) { - // 渲染当前页(1.5f=缩放比例,值越大清晰度越高) - java.awt.image.BufferedImage image = renderer.renderImage(pageNum, 1.5f); - - // 2. 图片转为输入流 - ByteArrayOutputStream os = new ByteArrayOutputStream(); - boolean writeSuccess = javax.imageio.ImageIO.write(image, "png", os); - if (!writeSuccess) { - document.close(); // 确保关闭文档 - return; - } - InputStream imageInputStream = new ByteArrayInputStream(os.toByteArray()); + System.out.println("正在处理第 " + (pageNum + 1) + "/" + totalPages + " 页..."); - // 3. 上传当前页图片至OSS(文件名包含页号,避免冲突) - String fileName = uuid + "_page" + (pageNum + 1) + ".png"; // 格式:uuid_page1.png - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); + try { + // 渲染当前页 + BufferedImage image = renderer.renderImage(pageNum, 1.5f); + System.out.println("页面渲染成功,尺寸: " + image.getWidth() + "x" + image.getHeight()); - FileInfo fileInfo = fileStorageService.of(imageInputStream) - .setPlatform("aliyun-oss-1") - .setPath(objectName) - .setOriginalFilename(fileName) - .setContentType("image/png") - .upload(); + // 图片转为字节数组 + ByteArrayOutputStream os = new ByteArrayOutputStream(); + boolean writeSuccess = ImageIO.write(image, "png", os); - if (fileInfo == null || fileInfo.getUrl() == null) { - document.close(); - return; + if (!writeSuccess) { + System.err.println("PNG格式写入失败,尝试JPEG格式"); + writeSuccess = ImageIO.write(image, "jpg", os); + if (!writeSuccess) { + System.err.println("无法将PDF页面 " + (pageNum + 1) + " 写入图片流"); + continue; + } + } + + byte[] imageBytes = os.toByteArray(); + System.out.println("图片字节数: " + imageBytes.length); + + if (imageBytes == null || imageBytes.length == 0) { + System.err.println("图片字节数组为空"); + continue; + } + + // 上传当前页图片 + String fileName = uuid + "_page" + (pageNum + 1) + ".png"; + System.out.println("上传文件名: " + fileName); + + MultipartFile multipartFile = new InputStreamMultipartFile( + "file", fileName, "image/png", imageBytes); + + String uploadedFileName = null; + try { + uploadedFileName = FileUploadUtils.upload(filePath, multipartFile); + System.out.println("文件上传成功,返回的文件名: " + uploadedFileName); + } catch (Exception e) { + System.err.println("调用FileUploadUtils.upload时出错: " + e.getMessage()); + e.printStackTrace(); + continue; + } + + if (uploadedFileName == null || uploadedFileName.isEmpty()) { + System.err.println("上传图片失败,返回的文件名为空"); + continue; + } + + // 构建完整访问URL + String fileUrl = buildImageUrl(serverUrl, uploadedFileName); + System.out.println("图片访问URL: " + fileUrl); + imageUrls.add(fileUrl); + + } catch (Exception e) { + System.err.println("处理PDF页面失败,页号: " + (pageNum + 1)); + e.printStackTrace(); } - imageUrls.add(fileInfo.getUrl()); // 直接使用上传后的URL } - document.close(); // 所有页处理完毕后关闭文档 - // 4. 存储多图片URL至数据库(逗号分隔) - article.setPdfImagePaths(String.join(",", imageUrls)); - System.out.println("文章PDF图片:" + article.getPdfImagePaths()); + // 6. 设置图片路径 + if (!imageUrls.isEmpty()) { + String imagePaths = String.join(",", imageUrls); + article.setPdfImagePaths(imagePaths); + System.out.println("成功生成 " + imageUrls.size() + " 张图片"); + System.out.println("图片路径: " + imagePaths); + } else { + System.err.println("没有生成任何图片"); + } + } catch (Exception e) { - System.err.println("PDF解析失败: " + e.getMessage()); + System.err.println("PDF解析失败:"); + e.printStackTrace(); + } finally { + // 确保文档关闭 + if (document != null) { + try { + document.close(); + } catch (IOException e) { + System.err.println("关闭PDF文档失败: " + e.getMessage()); + } + } + } + } + + /** + * 构建图片URL + */ + private String buildImageUrl(String serverUrl, String uploadedFileName) { + if (uploadedFileName == null || uploadedFileName.isEmpty()) { + return null; } + + // 如果已经是完整URL,直接返回 + if (uploadedFileName.startsWith("http")) { + return uploadedFileName; + } + + // 处理相对路径 + String normalizedFileName = uploadedFileName; + + // 去掉开头的斜杠(如果存在) + if (normalizedFileName.startsWith("/")) { + normalizedFileName = normalizedFileName.substring(1); + } + + // 检查是否已经包含profile/upload + if (normalizedFileName.startsWith("profile/upload/") || normalizedFileName.startsWith("profile/")) { + return serverUrl + normalizedFileName; + } + + // 否则添加profile/upload前缀 + // 注意:根据RuoYi的默认配置,上传的文件会放在profile/upload目录下 + return serverUrl + "profile/upload/" + normalizedFileName; } } \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/RecoverStationServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/RecoverStationServiceImpl.java index ddbbd10ae23015074ca21f37d039c68336985bbf..a7120eb91ad0a300ae4f512f56e51f9f4e15c398 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/service/impl/RecoverStationServiceImpl.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/service/impl/RecoverStationServiceImpl.java @@ -91,6 +91,9 @@ public class RecoverStationServiceImpl extends ServiceImpl maxRetries) { + // 达到最大重试次数 + log.error("【视频上传最终失败】已达到最大重试次数{}, ID: {}", maxRetries, riverView.getId()); + break; + } + + // 等待一段时间后重试 + try { + log.info("等待{}秒后重试...", 1); + Thread.sleep(1000); + } catch (InterruptedException ie) { + log.error("重试等待被中断"); + Thread.currentThread().interrupt(); + break; + } + } } + + log.info("========== 异步上传视频任务结束 =========="); } - @Async + /** + * 异步上传封面图 - 上传到远程服务器 + */ + @Async("pdfParseTaskExecutor") public void asyncUploadCoverImg(RiverView riverView, byte[] fileBytes, String originalfilename) { try { - String basePath = "cover_imgFiles/"; String newFileName = generateUniqueFileName(originalfilename); - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + newFileName; - InputStream inputStream = new ByteArrayInputStream(fileBytes); - FileInfo fileInfo = fileStorageService.of(inputStream, objectName).setPath(objectName).upload(); - riverView.setCover_img(fileInfo.getUrl()); + // 使用远程服务器上传 + String fileUrl = FileUploadUtil.uploadStreamToRemoteServer( + sftpProperties.toSftpConfig(), + inputStream, + newFileName, + "cover_imgFiles" + ); + + riverView.setCover_img(fileUrl); if (riverView.getId() == null || riverViewService.selectRiverViewById(riverView.getId()) == null) { riverViewService.insertRiverView(riverView); @@ -78,110 +181,500 @@ public class AsyncUploadService { riverViewService.updateRiverView(riverView); } - System.out.println("【封面图上传成功】" + fileInfo.getUrl()); + System.out.println("【封面图上传成功】" + fileUrl); } catch (Exception e) { System.err.println("【封面图上传失败】" + e.getMessage()); e.printStackTrace(); } } - //partyFile视频上传 - @Async + // ==================== 原来的党课视频上传方法(已注释) ==================== + // /** + // * 党课视频异步上传 - 上传到远程服务器(原方法,速度较慢) + // */ + // @Async("uploadTaskExecutor") + // public void asyncUploadPartyFile(Partyclass partyclass, byte[] fileBytes, String originalFilename) { + // int maxRetries = 3; // 最大重试次数 + // int retryCount = 0; + // + // while (retryCount <= maxRetries) { + // try { + // log.info("【开始上传视频】ID: {}, 文件名: {}, 第{}次尝试", + // partyclass.getId(), originalFilename, retryCount + 1); + // + // String newFileName = generateUniqueFileName(originalFilename); + // InputStream inputStream = new ByteArrayInputStream(fileBytes); + // + // // 使用远程服务器上传 + // String fileUrl = FileUploadUtil.uploadStreamToRemoteServer( + // sftpProperties.toSftpConfig(), + // inputStream, + // newFileName, + // "partyFiles" + // ); + // + // log.info("【视频上传完成】URL: {}", fileUrl); + // + // // 只更新视频文件字段 + // Partyclass updatePartyclass = new Partyclass(); + // updatePartyclass.setId(partyclass.getId()); + // updatePartyclass.setFile(fileUrl); + // + // // 执行更新 + // int result = partyclassService.updatePartyclass(updatePartyclass); + // + // log.info("【视频数据库更新结果】result: {}, ID: {}, URL: {}", result, partyclass.getId(), fileUrl); + // + // if (result > 0) { + // log.info("【视频上传成功】{}", fileUrl); + // return; // 成功则退出重试循环 + // } else { + // log.error("【视频数据库更新失败】ID: {}", partyclass.getId()); + // break; // 数据库更新失败,不再重试 + // } + // } catch (Exception e) { + // retryCount++; + // log.error("【视频上传失败】第{}次尝试失败, ID: {}, 错误信息: {}", + // retryCount, partyclass.getId(), e.getMessage(), e); + // + // if (retryCount > maxRetries) { + // // 达到最大重试次数,更新错误状态 + // log.error("【视频上传最终失败】已达到最大重试次数{}, ID: {}", maxRetries, partyclass.getId()); + // try { + // Partyclass errorUpdate = new Partyclass(); + // errorUpdate.setId(partyclass.getId()); + // errorUpdate.setFile("upload_failed"); + // partyclassService.updatePartyclass(errorUpdate); + // log.info("【更新错误状态成功】ID: {}", partyclass.getId()); + // } catch (Exception ex) { + // log.error("【更新错误状态失败】ID: {}", partyclass.getId(), ex); + // } + // break; + // } + // + // // 等待一段时间后重试 + // try { + // Thread.sleep(1000 ); // 递增等待时间 + // } catch (InterruptedException ie) { + // Thread.currentThread().interrupt(); + // break; + // } + // } + // } + // } + + /** + * 党课视频异步上传 - 使用分块上传到远程服务器(优化版) + */ + @Async("uploadTaskExecutor") public void asyncUploadPartyFile(Partyclass partyclass, byte[] fileBytes, String originalFilename) { - try { - System.out.println("【开始上传视频】ID: " + partyclass.getId() + ", 文件名: " + originalFilename); - - String basePath = "partyFiles/"; - String newFileName = generateUniqueFileName(originalFilename); - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + newFileName; + log.info("========== 开始异步上传任务 =========="); + log.info("线程名称: {}", Thread.currentThread().getName()); + log.info("记录ID: {}", partyclass.getId()); + log.info("文件名: {}", originalFilename); + log.info("文件大小: {} bytes ({} MB)", fileBytes.length, fileBytes.length / 1024 / 1024); - InputStream inputStream = new ByteArrayInputStream(fileBytes); + int maxRetries = 3; // 最大重试次数 + int retryCount = 0; - FileInfo fileInfo = fileStorageService.of(inputStream, objectName).setPath(objectName).upload(); - - System.out.println("【视频上传完成】URL: " + fileInfo.getUrl()); - - // 只更新视频文件字段 - Partyclass updatePartyclass = new Partyclass(); - updatePartyclass.setId(partyclass.getId()); - updatePartyclass.setFile(fileInfo.getUrl()); - - // 执行更新 - int result = partyclassService.updatePartyclass(updatePartyclass); - - System.out.println("【视频数据库更新结果】result: " + result + ", ID: " + partyclass.getId() + ", URL: " + fileInfo.getUrl()); - - if (result > 0) { - System.out.println("【视频上传成功】" + fileInfo.getUrl()); - } else { - System.err.println("【视频数据库更新失败】ID: " + partyclass.getId()); - } - } catch (Exception e) { - System.err.println("【视频上传失败】" + e.getMessage()); - e.printStackTrace(); - - // 发生异常时也更新状态,避免前端一直等待 + while (retryCount <= maxRetries) { try { - Partyclass errorUpdate = new Partyclass(); - errorUpdate.setId(partyclass.getId()); - errorUpdate.setFile("upload_failed"); - partyclassService.updatePartyclass(errorUpdate); - } catch (Exception ex) { - System.err.println("【更新错误状态失败】" + ex.getMessage()); + log.info("【开始分块上传党课视频】ID: {}, 文件名: {}, 文件大小: {} bytes, 第{}次尝试", + partyclass.getId(), originalFilename, fileBytes.length, retryCount + 1); + + String newFileName = generateUniqueFileName(originalFilename); + log.info("生成的新文件名: {}", newFileName); + + InputStream inputStream = new ByteArrayInputStream(fileBytes); + +// // 使用分块上传到远程服务器 +// String fileUrl = ChunkedFileUploadUtil.uploadStreamInChunks( +// sftpProperties.toSftpConfig(), +// inputStream, +// newFileName, +// "partyFiles", +// fileBytes.length +// ); + + String fileUrl = ""; + log.info("【党课视频分块上传完成】URL: {}", fileUrl); + + // 只更新视频文件字段 + Partyclass updatePartyclass = new Partyclass(); + updatePartyclass.setId(partyclass.getId()); + updatePartyclass.setFile(fileUrl); + + log.info("准备更新数据库,ID: {}, URL: {}", partyclass.getId(), fileUrl); + + // 执行更新 + int result = partyclassService.updatePartyclass(updatePartyclass); + + log.info("【视频数据库更新结果】result: {}, ID: {}, URL: {}", result, partyclass.getId(), fileUrl); + + if (result > 0) { + log.info("【党课视频上传成功】{}", fileUrl); + log.info("========== 异步上传任务完成 =========="); + return; // 成功则退出重试循环 + } else { + log.error("【视频数据库更新失败】ID: {}, result: {}", partyclass.getId(), result); + + // 查询记录是否存在 + try { + Partyclass existingRecord = partyclassService.selectPartyclassById(partyclass.getId()); + if (existingRecord == null) { + log.error("【记录不存在】ID: {}", partyclass.getId()); + } else { + log.info("【记录存在】ID: {}, 当前file值: {}", partyclass.getId(), existingRecord.getFile()); + } + } catch (Exception ex) { + log.error("【查询记录失败】ID: {}", partyclass.getId(), ex); + } + + break; // 数据库更新失败,不再重试 + } + } catch (Exception e) { + retryCount++; + log.error("【党课视频分块上传失败】第{}次尝试失败, ID: {}, 错误类型: {}, 错误信息: {}", + retryCount, partyclass.getId(), e.getClass().getSimpleName(), e.getMessage()); + log.error("异常堆栈:", e); + + if (retryCount > maxRetries) { + // 达到最大重试次数,更新错误状态 + log.error("【党课视频上传最终失败】已达到最大重试次数{}, ID: {}", maxRetries, partyclass.getId()); + try { + Partyclass errorUpdate = new Partyclass(); + errorUpdate.setId(partyclass.getId()); + errorUpdate.setFile("upload_failed"); + partyclassService.updatePartyclass(errorUpdate); + log.info("【更新错误状态成功】ID: {}", partyclass.getId()); + } catch (Exception ex) { + log.error("【更新错误状态失败】ID: {}", partyclass.getId(), ex); + } + break; + } + + // 等待一段时间后重试 + try { + log.info("等待{}秒后重试...", 1); + Thread.sleep(1000); + } catch (InterruptedException ie) { + log.error("重试等待被中断"); + Thread.currentThread().interrupt(); + break; + } } } + + log.info("========== 异步上传任务结束 =========="); } - - //partyFile封面图片上传 - @Async - public void asyncUploadPartyCoverImg(Partyclass partyclass, byte[] fileBytes, String originalFilename) { - try { - System.out.println("【开始上传封面图片】ID: " + partyclass.getId() + ", 文件名: " + originalFilename); - - String basePath = "partyCoverFiles/"; - String newFileName = generateUniqueFileName(originalFilename); - String objectName = basePath + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + newFileName; - InputStream inputStream = new ByteArrayInputStream(fileBytes); - FileInfo fileInfo = fileStorageService.of(inputStream, objectName).setPath(objectName).upload(); - - System.out.println("【封面图片上传完成】URL: " + fileInfo.getUrl()); - - // 只更新封面图片字段 - Partyclass updatePartyclass = new Partyclass(); - updatePartyclass.setId(partyclass.getId()); - updatePartyclass.setCoverImg(fileInfo.getUrl()); - - // 执行更新 - int result = partyclassService.updatePartyclass(updatePartyclass); - - System.out.println("【封面图片数据库更新结果】result: " + result + ", ID: " + partyclass.getId() + ", URL: " + fileInfo.getUrl()); - - if (result > 0) { - System.out.println("【封面图片上传成功】" + fileInfo.getUrl()); - } else { - System.err.println("【封面图片数据库更新失败】ID: " + partyclass.getId()); - } - } catch (Exception e) { - System.err.println("【封面图片上传失败】" + e.getMessage()); - e.printStackTrace(); - - // 发生异常时也更新状态,避免前端一直等待 + /** + * 党课封面图片异步上传 - 上传到远程服务器 + */ + @Async("pdfParseTaskExecutor") + public void asyncUploadPartyCoverImg(Partyclass partyclass, byte[] fileBytes, String originalFilename) { + int maxRetries = 3; // 最大重试次数 + int retryCount = 0; + + while (retryCount <= maxRetries) { try { - Partyclass errorUpdate = new Partyclass(); - errorUpdate.setId(partyclass.getId()); - errorUpdate.setCoverImg("upload_failed"); - partyclassService.updatePartyclass(errorUpdate); - } catch (Exception ex) { - System.err.println("【更新错误状态失败】" + ex.getMessage()); + log.info("【开始上传封面图片】ID: {}, 文件名: {}, 第{}次尝试", + partyclass.getId(), originalFilename, retryCount + 1); + + String newFileName = generateUniqueFileName(originalFilename); + InputStream inputStream = new ByteArrayInputStream(fileBytes); + + // 使用远程服务器上传 + String fileUrl = FileUploadUtil.uploadStreamToRemoteServer( + sftpProperties.toSftpConfig(), + inputStream, + newFileName, + "partyCoverFiles" + ); + + log.info("【封面图片上传完成】URL: {}", fileUrl); + + // 只更新封面图片字段 + Partyclass updatePartyclass = new Partyclass(); + updatePartyclass.setId(partyclass.getId()); + updatePartyclass.setCoverImg(fileUrl); + + // 执行更新 + int result = partyclassService.updatePartyclass(updatePartyclass); + + log.info("【封面图片数据库更新结果】result: {}, ID: {}, URL: {}", result, partyclass.getId(), fileUrl); + + if (result > 0) { + log.info("【封面图片上传成功】{}", fileUrl); + return; // 成功则退出重试循环 + } else { + log.error("【封面图片数据库更新失败】ID: {}", partyclass.getId()); + break; // 数据库更新失败,不再重试 + } + } catch (Exception e) { + retryCount++; + log.error("【封面图片上传失败】第{}次尝试失败, ID: {}, 错误信息: {}", + retryCount, partyclass.getId(), e.getMessage(), e); + + if (retryCount > maxRetries) { + // 达到最大重试次数,更新错误状态 + log.error("【封面图片上传最终失败】已达到最大重试次数{}, ID: {}", maxRetries, partyclass.getId()); + try { + Partyclass errorUpdate = new Partyclass(); + errorUpdate.setId(partyclass.getId()); + errorUpdate.setCoverImg("upload_failed"); + partyclassService.updatePartyclass(errorUpdate); + log.info("【更新错误状态成功】ID: {}", partyclass.getId()); + } catch (Exception ex) { + log.error("【更新错误状态失败】ID: {}", partyclass.getId(), ex); + } + break; + } + + // 等待一段时间后重试 + try { + Thread.sleep(1000); // 递增等待时间 + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + break; + } } } } + private String generateUniqueFileName(String originalFilename) { String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); return UUID.randomUUID() + ext; } + + + /** + * 异步上传审批表文件 + */ + @Async("uploadTaskExecutor") + public void asyncUploadAppvalFile(Long id, MultipartFile appvalPath) { + // 参数校验 + if (id == null || appvalPath == null || appvalPath.isEmpty()) { + log.warn("【异步上传审批表文件】参数无效,ID: {}, 文件为空: {}", id, appvalPath == null ? "true" : appvalPath.isEmpty()); + return; + } + + int maxRetries = 3; + int retryCount = 0; + + while (retryCount <= maxRetries) { + try { + log.info("【开始异步上传审批表文件】ID: {}, 文件名: {}, 第{}次尝试", + id, appvalPath.getOriginalFilename(), retryCount + 1); + + String fileUrl = FileUploadUtil.uploadFileToRemoteServer( + sftpProperties.toSftpConfig(), + appvalPath, + "articleFilePath/appvalPath" + ); + + Article article = new Article(); + article.setArticleId(id); + article.setAppval(fileUrl); + + boolean result = articleService.updateById(article); + if (result) { + log.info("【异步审批表文件上传成功】ID: {}, URL: {}", id, fileUrl); + } else { + log.error("【异步审批表文件数据库更新失败】ID: {}", id); + } + return; // 成功则退出 + + } catch (Exception e) { + retryCount++; + log.error("【异步审批表文件上传失败】第{}次尝试失败, ID: {}, 错误信息: {}", + retryCount, id, e.getMessage(), e); + + if (retryCount > maxRetries) { + log.error("【异步审批表文件上传最终失败】已达到最大重试次数{}, ID: {}", maxRetries, id); + return; + } + + // 等待后重试 + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + return; + } + } + } + } + + /** + * 异步上传封面图片 + */ + @Async("pdfParseTaskExecutor") + public void asyncUploadCoverImage(Long id, MultipartFile file) { + // 参数校验 + if (id == null || file == null || file.isEmpty()) { + log.warn("【异步上传封面图片】参数无效,ID: {}, 文件为空: {}", id, file == null ? "true" : file.isEmpty()); + return; + } + + int maxRetries = 3; + int retryCount = 0; + + while (retryCount <= maxRetries) { + try { + log.info("【开始异步上传封面图片】ID: {}, 文件名: {}, 第{}次尝试", + id, file.getOriginalFilename(), retryCount + 1); + + String fileUrl = FileUploadUtil.uploadFileToRemoteServer( + sftpProperties.toSftpConfig(), + file, + "articleFilePath/filePath" + ); + + Article article = new Article(); + article.setArticleId(id); + article.setCoverPath(fileUrl); + + int result = articleMapper.updateArticle(article); + if (result > 0) { + log.info("【异步封面图片上传成功】ID: {}, URL: {}", id, fileUrl); + } else { + log.error("【异步封面图片数据库更新失败】ID: {}", id); + } + return; // 成功则退出 + + } catch (Exception e) { + retryCount++; + log.error("【异步封面图片上传失败】第{}次尝试失败, ID: {}, 错误信息: {}", + retryCount, id, e.getMessage(), e); + + if (retryCount > maxRetries) { + log.error("【异步封面图片上传最终失败】已达到最大重试次数{}, ID: {}", maxRetries, id); + return; + } + + // 等待后重试 + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + return; + } + } + } + } + + /** + * 异步上传文章内容图片 + */ + @Async("pdfParseTaskExecutor") + public void asyncUploadContentImage(MultipartFile file, String imageUrlCallback) { + // 参数校验 + if (file == null || file.isEmpty()) { + log.warn("【异步上传文章内容图片】文件为空"); + return; + } + + int maxRetries = 3; + int retryCount = 0; + + while (retryCount <= maxRetries) { + try { + log.info("【开始异步上传文章内容图片】文件名: {}, 第{}次尝试", + file.getOriginalFilename(), retryCount + 1); + + String fileUrl = FileUploadUtil.uploadFileToRemoteServer( + sftpProperties.toSftpConfig(), + file, + "articleFilePath/contentImage" + ); + + log.info("【异步文章内容图片上传成功】URL: {}", fileUrl); + // 可以通过回调或其他方式通知前端图片上传完成 + return; // 成功则退出 + + } catch (Exception e) { + retryCount++; + log.error("【异步文章内容图片上传失败】第{}次尝试失败, 错误信息: {}", + retryCount, e.getMessage(), e); + + if (retryCount > maxRetries) { + log.error("【异步文章内容图片上传最终失败】已达到最大重试次数{}", maxRetries); + return; + } + + // 等待后重试 + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + return; + } + } + } + } + + /** + * 异步上传附件 + */ + @Async("uploadTaskExecutor") + public void asyncUploadAttachment(Long id, MultipartFile file) { + // 参数校验 + if (id == null || file == null || file.isEmpty()) { + log.warn("【异步上传附件】参数无效,ID: {}, 文件为空: {}", id, file == null ? "true" : file.isEmpty()); + return; + } + + int maxRetries = 3; + int retryCount = 0; + + while (retryCount <= maxRetries) { + try { + log.info("【开始异步上传附件】ID: {}, 文件名: {}, 第{}次尝试", + id, file.getOriginalFilename(), retryCount + 1); + + String fileUrl = FileUploadUtil.uploadFileToRemoteServer( + sftpProperties.toSftpConfig(), + file, + "articleFilePath/attachment" + ); + + Article article = articleService.selectArticleByArticleId(id); + if (article != null) { + article.setAttachment(fileUrl); + int result = articleMapper.updateArticle(article); + if (result > 0) { + log.info("【异步附件上传成功】ID: {}, URL: {}", id, fileUrl); + } else { + log.error("【异步附件数据库更新失败】ID: {}", id); + } + } else { + log.warn("【异步附件上传】文章不存在,ID: {}", id); + } + return; // 成功则退出 + + } catch (Exception e) { + retryCount++; + log.error("【异步附件上传失败】第{}次尝试失败, ID: {}, 错误信息: {}", + retryCount, id, e.getMessage(), e); + + if (retryCount > maxRetries) { + log.error("【异步附件上传最终失败】已达到最大重试次数{}, ID: {}", maxRetries, id); + return; + } + + // 等待后重试 + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + return; + } + } + } + } + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/util/ChunkedFileUploadUtil.java b/ruoyi-admin/src/main/java/com/ruoyi/util/ChunkedFileUploadUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..97a19496d9eb1b9436ebac4b81b813062aa9c479 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/util/ChunkedFileUploadUtil.java @@ -0,0 +1,712 @@ +package com.ruoyi.util; + +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +/** + * 分块文件上传工具类 + * 用于将大文件分块上传到远程服务器,提高上传速度和稳定性 + */ +@Slf4j +public class ChunkedFileUploadUtil { + + // 默认分块大小:5MB + private static final int DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024; + + /** + * 分块上传文件到远程服务器 + * + * @param sftpConfig SFTP配置信息 + * @param file 要上传的文件 + * @param module 模块名称(用于创建子目录) + * @return 文件访问URL + * @throws Exception 上传异常 + */ + public static String uploadFileInChunks(FileUploadUtil.SftpConfig sftpConfig, + MultipartFile file, String module) throws Exception { + return uploadFileInChunks(sftpConfig, file, module, DEFAULT_CHUNK_SIZE); + } + + /** + * 分块上传文件到远程服务器(自定义分块大小) + * + * @param sftpConfig SFTP配置信息 + * @param file 要上传的文件 + * @param module 模块名称(用于创建子目录) + * @param chunkSize 分块大小(字节) + * @return 文件访问URL + * @throws Exception 上传异常 + */ + public static String uploadFileInChunks(FileUploadUtil.SftpConfig sftpConfig, + MultipartFile file, String module, int chunkSize) throws Exception { + JSch jsch = new JSch(); + Session session = null; + ChannelSftp sftpChannel = null; + + try { + // 建立SSH连接 + session = jsch.getSession(sftpConfig.getUsername(), sftpConfig.getHost(), sftpConfig.getPort()); + session.setPassword(sftpConfig.getPassword()); + session.setConfig("StrictHostKeyChecking", "no"); + session.connect(30000); // 30秒超时 + + // 打开SFTP通道 + sftpChannel = (ChannelSftp) session.openChannel("sftp"); + sftpChannel.connect(30000); // 30秒超时 + + // 生成远程文件路径 + String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); + String remoteDir = sftpConfig.getPath() + module + "/" + datePath; + String fileName = file.getOriginalFilename(); + String remoteFilePath = remoteDir + "/" + fileName; + + // 创建远程目录(递归创建) + createRemoteDirs(sftpChannel, remoteDir); + + // 获取文件大小 + long fileSize = file.getSize(); + log.info("【开始分块上传】文件名: {}, 文件大小: {} bytes, 分块大小: {} bytes", + fileName, fileSize, chunkSize); + + // 如果文件小于分块大小,直接上传 + if (fileSize <= chunkSize) { + log.info("【文件较小,直接上传】文件名: {}", fileName); + InputStream inputStream = file.getInputStream(); + sftpChannel.put(inputStream, remoteFilePath); + inputStream.close(); + } else { + // 分块上传 + uploadInChunks(sftpChannel, file, remoteFilePath, chunkSize); + } + + // 构造可访问的URL + String fileUrl = "http://" + sftpConfig.getHost() + ":9010/riverFiles/" + module + "/" + datePath + "/" + fileName; + log.info("【分块上传完成】文件URL: {}", fileUrl); + + return fileUrl; + + } finally { + // 关闭连接 + if (sftpChannel != null && sftpChannel.isConnected()) { + sftpChannel.disconnect(); + } + if (session != null && session.isConnected()) { + session.disconnect(); + } + } + } + + + + + //*********************************************************************************** + /** + * 分块上传InputStream到远程服务器 + * + * @param sftpConfig SFTP配置信息 + * @param inputStream 输入流 + * @param fileName 文件名 + * @param module 模块名称(用于创建子目录) + * @param fileSize 文件大小 + * @return 文件访问URL + * @throws Exception 上传异常 + */ +// public static String uploadStreamInChunks(FileUploadUtil.SftpConfig sftpConfig, +// InputStream inputStream, String fileName, +// String module, long fileSize) throws Exception { +// return uploadStreamInChunks(sftpConfig, inputStream, fileName, module, fileSize, DEFAULT_CHUNK_SIZE); +// } + + /** + * 分块上传InputStream到远程服务器(自定义分块大小) + * + * @param sftpConfig SFTP配置信息 + * @param inputStream 输入流 + * @param fileName 文件名 + * @param module 模块名称(用于创建子目录) + * @param fileSize 文件大小 + * @param chunkSize 分块大小(字节) + * @return 文件访问URL + * @throws Exception 上传异常 + */ +// public static String uploadStreamInChunks(FileUploadUtil.SftpConfig sftpConfig, +// InputStream inputStream, String fileName, +// String module, long fileSize, int chunkSize) throws Exception { +// JSch jsch = new JSch(); +// Session session = null; +// ChannelSftp sftpChannel = null; +// +// try { +// // 建立SSH连接 +// session = jsch.getSession(sftpConfig.getUsername(), sftpConfig.getHost(), sftpConfig.getPort()); +// session.setPassword(sftpConfig.getPassword()); +// session.setConfig("StrictHostKeyChecking", "no"); +// +// // 设置连接保活参数,防止长时间操作时连接断开 +// session.setServerAliveInterval(10000); // 每10秒发送一次心跳 +// session.setServerAliveCountMax(6); // 最多6次心跳失败后断开(60秒) +// +// session.connect(30000); // 30秒超时 +// +// // 打开SFTP通道 +// sftpChannel = (ChannelSftp) session.openChannel("sftp"); +// sftpChannel.connect(30000); // 30秒超时 +// +// // 生成远程文件路径 +// String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); +// String remoteDir = sftpConfig.getPath() + module + "/" + datePath; +// String remoteFilePath = remoteDir + "/" + fileName; +// +// // 创建远程目录(递归创建) +// createRemoteDirs(sftpChannel, remoteDir); +// +// log.info("【开始分块上传流】文件名: {}, 文件大小: {} bytes, 分块大小: {} bytes", +// fileName, fileSize, chunkSize); +// +// // 如果文件小于分块大小,直接上传 +// if (fileSize <= chunkSize) { +// log.info("【文件较小,直接上传】文件名: {}", fileName); +// sftpChannel.put(inputStream, remoteFilePath); +// } else { +// // 分块上传 +// uploadStreamInChunksInternal(sftpChannel, inputStream, remoteFilePath, fileSize, chunkSize); +// } +// +// inputStream.close(); +// +// // 构造可访问的URL +// String fileUrl = "http://" + sftpConfig.getHost() + ":9010/riverFiles/" + module + "/" + datePath + "/" + fileName; +// log.info("【分块上传流完成】文件URL: {}", fileUrl); +// +// return fileUrl; +// +// } finally { +// // 关闭连接 +// if (sftpChannel != null && sftpChannel.isConnected()) { +// sftpChannel.disconnect(); +// } +// if (session != null && session.isConnected()) { +// session.disconnect(); +// } +// } +// } +//************************************************************************************ + + /** + * 本地分块上传文件 + * + * @param file 要上传的文件 + * @param localBasePath 本地基础路径 + * @param module 模块名称(用于创建子目录) + * @return 文件访问路径 + * @throws Exception 上传异常 + */ + public static String uploadFileLocallyInChunks(MultipartFile file, String localBasePath, String module) throws Exception { + return uploadFileLocallyInChunks(file, localBasePath, module, DEFAULT_CHUNK_SIZE); + } + + /** + * 本地分块上传文件(自定义分块大小) + * + * @param file 要上传的文件 + * @param localBasePath 本地基础路径 + * @param module 模块名称(用于创建子目录) + * @param chunkSize 分块大小(字节) + * @return 文件访问路径 + * @throws Exception 上传异常 + */ + public static String uploadFileLocallyInChunks(MultipartFile file, String localBasePath, String module, int chunkSize) throws Exception { + // 生成本地文件路径 + String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); + String localDir = localBasePath + File.separator + module + File.separator + datePath; + String fileName = file.getOriginalFilename(); + String localFilePath = localDir + File.separator + fileName; + + // 创建本地目录 + createLocalDirs(localDir); + + // 获取文件大小 + long fileSize = file.getSize(); + log.info("【开始本地分块上传】文件名: {}, 文件大小: {} bytes, 分块大小: {} bytes", + fileName, fileSize, chunkSize); + + // 如果文件小于分块大小,直接上传 + if (fileSize <= chunkSize) { + log.info("【文件较小,直接上传】文件名: {}", fileName); + try (InputStream inputStream = file.getInputStream()) { + saveFileLocally(inputStream, localFilePath); + } + } else { + // 分块上传 + uploadLocallyInChunks(file, localFilePath, chunkSize); + } + + return localFilePath; + } + + /** + * 执行本地分块上传 + */ + private static void uploadLocallyInChunks(MultipartFile file, String localFilePath, int chunkSize) throws Exception { + try (InputStream inputStream = file.getInputStream()) { + long fileSize = file.getSize(); + uploadStreamLocallyInChunks(inputStream, localFilePath, fileSize, chunkSize); + } + } + + /** + * 执行本地分块上传(InputStream) + */ + private static void uploadStreamLocallyInChunks(InputStream inputStream, String localFilePath, + long fileSize, int chunkSize) throws Exception { + // 创建临时目录用于分块 + String tempDir = localFilePath + ".chunks"; + List chunkFiles = new ArrayList<>(); + + try { + createLocalDirs(tempDir); + + // 分块上传 + int chunkIndex = 0; + long uploadedBytes = 0; + byte[] buffer = new byte[chunkSize]; + int bytesRead; + + while ((bytesRead = inputStream.read(buffer)) != -1) { + String chunkFileName = tempDir + File.separator + "chunk_" + chunkIndex; + chunkFiles.add(chunkFileName); + + // 保存分块 + try (ByteArrayInputStream chunkStream = new ByteArrayInputStream(buffer, 0, bytesRead)) { + saveFileLocally(chunkStream, chunkFileName); + } + + uploadedBytes += bytesRead; + chunkIndex++; + + // 计算进度 + int progress = (int) ((uploadedBytes * 100) / fileSize); + log.info("【本地上传进度】{}% ({}/{} bytes), 分块 {}/{}", + progress, uploadedBytes, fileSize, chunkIndex, + (fileSize + chunkSize - 1) / chunkSize); + } + + log.info("【本地分块上传完成】共上传 {} 个分块", chunkIndex); + + // 合并分块 + mergeLocalChunks(chunkFiles, localFilePath); + + } finally { + // 清理临时文件 + cleanupLocalChunks(chunkFiles, tempDir); + } + } + + /** + * 保存文件到本地 + */ + private static void saveFileLocally(InputStream inputStream, String filePath) throws IOException { + File file = new File(filePath); + try (OutputStream outputStream = new FileOutputStream(file)) { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } + } + + /** + * 合并本地分块文件 + */ + private static void mergeLocalChunks(List chunkFiles, String targetFilePath) throws IOException { + log.info("【开始合并本地分块】目标文件: {}, 分块数: {}", targetFilePath, chunkFiles.size()); + + try (OutputStream outputStream = new FileOutputStream(targetFilePath)) { + for (int i = 0; i < chunkFiles.size(); i++) { + String chunkFile = chunkFiles.get(i); + + try (InputStream chunkStream = new FileInputStream(chunkFile)) { + byte[] buffer = new byte[65536]; + int bytesRead; + while ((bytesRead = chunkStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } + + if ((i + 1) % 10 == 0 || i == chunkFiles.size() - 1) { + log.info("【本地合并进度】{}/{}", i + 1, chunkFiles.size()); + } + } + } + + log.info("【本地分块合并完成】"); + } + + /** + * 清理本地临时分块文件 + */ + private static void cleanupLocalChunks(List chunkFiles, String tempDir) { + try { + int successCount = 0; + int failCount = 0; + + // 删除分块文件 + for (String chunkFile : chunkFiles) { + try { + File file = new File(chunkFile); + if (file.exists() && file.delete()) { + successCount++; + } else { + failCount++; + } + } catch (Exception e) { + failCount++; + log.debug("【清理本地分块失败】文件: {}", chunkFile, e); + } + } + + // 删除临时目录 + try { + File dir = new File(tempDir); + if (dir.exists() && dir.delete()) { + log.debug("【清理本地临时目录成功】目录: {}", tempDir); + } + } catch (Exception e) { + log.debug("【清理本地临时目录失败】目录: {}", tempDir, e); + } + + if (failCount == 0) { + log.info("【清理本地临时文件完成】成功清理 {} 个分块文件", successCount); + } else { + log.warn("【清理本地临时文件完成】成功: {}, 失败: {}", successCount, failCount); + } + + } catch (Exception e) { + log.error("【清理本地临时文件异常】", e); + } + } + + /** + * 创建本地目录 + */ + private static void createLocalDirs(String dirPath) { + File dir = new File(dirPath); + if (!dir.exists()) { + dir.mkdirs(); + } + } + + /** + * 执行分块上传(MultipartFile) + */ + private static void uploadInChunks(ChannelSftp sftpChannel, MultipartFile file, + String remoteFilePath, int chunkSize) throws Exception { + InputStream inputStream = file.getInputStream(); + long fileSize = file.getSize(); + + uploadStreamInChunksInternal(sftpChannel, inputStream, remoteFilePath, fileSize, chunkSize); + + inputStream.close(); + } + + /** + * 执行分块上传(InputStream) + */ + private static void uploadStreamInChunksInternal(ChannelSftp sftpChannel, InputStream inputStream, + String remoteFilePath, long fileSize, int chunkSize) throws Exception { + // 创建临时文件用于分块 + String tempDir = remoteFilePath + ".chunks"; + List chunkFiles = new ArrayList<>(); + + try { + // 创建临时目录 + try { + sftpChannel.mkdir(tempDir); + } catch (SftpException e) { + // 目录可能已存在,忽略 + } + + // 分块上传 + int chunkIndex = 0; + long uploadedBytes = 0; + byte[] buffer = new byte[chunkSize]; + int bytesRead; + + while ((bytesRead = inputStream.read(buffer)) != -1) { + String chunkFileName = tempDir + "/chunk_" + chunkIndex; + chunkFiles.add(chunkFileName); + + // 上传分块 + ByteArrayInputStream chunkStream = new ByteArrayInputStream(buffer, 0, bytesRead); + sftpChannel.put(chunkStream, chunkFileName); + chunkStream.close(); + + uploadedBytes += bytesRead; + chunkIndex++; + + // 计算进度 + int progress = (int) ((uploadedBytes * 100) / fileSize); + log.info("【上传进度】{}% ({}/{} bytes), 分块 {}/{}", + progress, uploadedBytes, fileSize, chunkIndex, + (fileSize + chunkSize - 1) / chunkSize); + } + + log.info("【分块上传完成】共上传 {} 个分块", chunkIndex); + + // 合并分块 + mergeChunks(sftpChannel, chunkFiles, remoteFilePath); + + } finally { + // 清理临时文件 + cleanupChunks(sftpChannel, chunkFiles, tempDir); + } + } + + /** + * 合并分块文件(使用SSH命令在服务器端合并,避免网络传输) + */ + private static void mergeChunks(ChannelSftp sftpChannel, List chunkFiles, + String targetFilePath) throws Exception { + log.info("【开始合并分块】目标文件: {}, 分块数: {}", targetFilePath, chunkFiles.size()); + + try { + // 方案1:尝试使用SSH命令在服务器端合并(更快,不需要网络传输) + Session session = sftpChannel.getSession(); + if (session != null && session.isConnected()) { + try { + mergeChunksViaSSH(session, chunkFiles, targetFilePath); + log.info("【分块合并完成】使用SSH命令合并"); + return; + } catch (Exception e) { + log.warn("【SSH合并失败】降级使用SFTP合并: {}", e.getMessage()); + } + } + } catch (Exception e) { + log.warn("【无法获取SSH会话】降级使用SFTP合并: {}", e.getMessage()); + } + + // 方案2:使用SFTP传输合并(兜底方案) + mergeChunksViaSFTP(sftpChannel, chunkFiles, targetFilePath); + log.info("【分块合并完成】使用SFTP传输合并"); + } + + /** + * 使用SSH命令在服务器端合并分块(推荐方式,速度快) + */ + private static void mergeChunksViaSSH(Session session, List chunkFiles, + String targetFilePath) throws Exception { + ChannelExec execChannel = null; + try { + execChannel = (ChannelExec) session.openChannel("exec"); + + // 构建cat命令,将所有分块合并到目标文件 + StringBuilder command = new StringBuilder("cat"); + for (String chunkFile : chunkFiles) { + command.append(" \"").append(chunkFile).append("\""); + } + command.append(" > \"").append(targetFilePath).append("\""); + + log.info("【执行SSH合并命令】{}", command.toString()); + execChannel.setCommand(command.toString()); + + // 获取错误输出 + InputStream errStream = execChannel.getErrStream(); + execChannel.connect(30000); + + // 等待命令执行完成 + while (!execChannel.isClosed()) { + Thread.sleep(100); + } + + int exitStatus = execChannel.getExitStatus(); + + // 读取错误输出 + byte[] tmp = new byte[1024]; + StringBuilder errorOutput = new StringBuilder(); + while (errStream.available() > 0) { + int i = errStream.read(tmp, 0, 1024); + if (i < 0) break; + errorOutput.append(new String(tmp, 0, i)); + } + + if (exitStatus != 0) { + throw new IOException("SSH合并命令执行失败,退出码: " + exitStatus + + ", 错误: " + errorOutput.toString()); + } + + log.info("【SSH合并成功】退出码: {}", exitStatus); + + } finally { + if (execChannel != null && execChannel.isConnected()) { + execChannel.disconnect(); + } + } + } + + /** + * 使用SFTP传输合并分块(兜底方案,速度较慢但更可靠) + */ + private static void mergeChunksViaSFTP(ChannelSftp sftpChannel, List chunkFiles, + String targetFilePath) throws Exception { + OutputStream outputStream = null; + try { + // 检查SFTP连接是否有效 + if (!sftpChannel.isConnected()) { + throw new IOException("SFTP连接已断开"); + } + + outputStream = sftpChannel.put(targetFilePath); + + for (int i = 0; i < chunkFiles.size(); i++) { + String chunkFile = chunkFiles.get(i); + InputStream chunkStream = null; + + try { + chunkStream = sftpChannel.get(chunkFile); + + byte[] buffer = new byte[65536]; // 增大缓冲区到64KB + int bytesRead; + while ((bytesRead = chunkStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + + if ((i + 1) % 10 == 0 || i == chunkFiles.size() - 1) { + log.info("【合并进度】{}/{}", i + 1, chunkFiles.size()); + } + + } finally { + if (chunkStream != null) { + try { + chunkStream.close(); + } catch (Exception e) { + log.debug("关闭分块流失败: {}", e.getMessage()); + } + } + } + } + + outputStream.flush(); + + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (Exception e) { + log.warn("关闭输出流失败: {}", e.getMessage()); + } + } + } + } + + /** + * 清理临时分块文件 + */ + private static void cleanupChunks(ChannelSftp sftpChannel, List chunkFiles, String tempDir) { + try { + // 检查SFTP连接是否有效 + if (sftpChannel == null || !sftpChannel.isConnected()) { + log.warn("【清理临时文件跳过】SFTP连接已关闭"); + return; + } + + int successCount = 0; + int failCount = 0; + boolean hasPermissionIssue = false; + + // 删除分块文件 + for (String chunkFile : chunkFiles) { + try { + sftpChannel.rm(chunkFile); + successCount++; + } catch (SftpException e) { + failCount++; + + // 错误码 4 (SSH_FX_FAILURE) 通常表示权限问题或文件被锁定 + if (e.id == 4) { + hasPermissionIssue = true; + // 只记录第一个权限错误,避免大量重复日志 + if (failCount == 1) { + log.warn("【清理分块失败】检测到权限问题(错误码: 4),可能是SFTP用户没有删除权限。" + + "这不影响视频上传功能,但会在服务器上留下临时文件。建议配置定时清理任务。"); + } + log.debug("【清理分块失败】文件: {}, 错误码: {}", chunkFile, e.id); + } else if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) { + // 文件不存在,算作成功 + log.debug("【分块文件已不存在】文件: {}", chunkFile); + successCount++; + failCount--; + } else { + // 其他错误,记录详细信息 + String errorMsg = e.getMessage() != null ? e.getMessage() : "未知错误"; + log.warn("【清理分块失败】文件: {}, 错误码: {}, 错误信息: {}", + chunkFile, e.id, errorMsg); + } + } + } + + // 删除临时目录 + try { + sftpChannel.rmdir(tempDir); + log.debug("【清理临时目录成功】目录: {}", tempDir); + } catch (SftpException e) { + if (e.id == 4) { + log.debug("【清理临时目录失败】目录: {}, 错误码: 4(权限问题)", tempDir); + } else if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) { + log.debug("【临时目录已不存在】目录: {}", tempDir); + } else { + String errorMsg = e.getMessage() != null ? e.getMessage() : "未知错误"; + log.warn("【清理临时目录失败】目录: {}, 错误码: {}, 错误信息: {}", + tempDir, e.id, errorMsg); + } + } + + // 根据清理结果输出不同级别的日志 + if (failCount == 0) { + log.info("【清理临时文件完成】成功清理 {} 个分块文件", successCount); + } else if (hasPermissionIssue) { + log.info("【清理临时文件完成】成功: {}, 失败: {} (权限问题,建议配置定时清理任务)", + successCount, failCount); + } else { + log.warn("【清理临时文件完成】成功: {}, 失败: {}, 总计: {}", + successCount, failCount, chunkFiles.size()); + } + + } catch (Exception e) { + log.error("【清理临时文件异常】", e); + } + } + + /** + * 递归创建远程目录 + */ + private static void createRemoteDirs(ChannelSftp sftpChannel, String dirPath) throws SftpException { + String[] folders = dirPath.split("/"); + StringBuilder path = new StringBuilder(); + + for (String folder : folders) { + if (folder.length() > 0) { + path.append("/").append(folder); + try { + sftpChannel.cd(path.toString()); + } catch (SftpException e) { + // 目录不存在,创建目录 + sftpChannel.mkdir(path.toString()); + sftpChannel.cd(path.toString()); + } + } + } + } +} + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/util/CustomMultipartFile.java b/ruoyi-admin/src/main/java/com/ruoyi/util/CustomMultipartFile.java new file mode 100644 index 0000000000000000000000000000000000000000..e6893f9cc6b157d8a59258857c431c7dc69c6ce7 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/util/CustomMultipartFile.java @@ -0,0 +1,59 @@ +package com.ruoyi.util; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; + +// 在AsyncUploadService内部添加这个内部类 +public class CustomMultipartFile implements MultipartFile { + private final byte[] content; + private final String originalFilename; + + public CustomMultipartFile(byte[] content, String originalFilename) { + this.content = content; + this.originalFilename = originalFilename; + } + + @Override + public String getName() { + return originalFilename; + } + + @Override + public String getOriginalFilename() { + return originalFilename; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public boolean isEmpty() { + return content == null || content.length == 0; + } + + @Override + public long getSize() { + return content.length; + } + + @Override + public byte[] getBytes() throws IOException { + return content; + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(content); + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + try (FileOutputStream fos = new FileOutputStream(dest)) { + fos.write(content); + } + } +} + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/util/FileUploadUtil.java b/ruoyi-admin/src/main/java/com/ruoyi/util/FileUploadUtil.java index cffb01c03cc31c2035498730a3dbde7c4d624d0c..12f2a7eb9fe2a8f6600f4bbaf923cb4231b5c673 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/util/FileUploadUtil.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/util/FileUploadUtil.java @@ -60,7 +60,8 @@ public class FileUploadUtil { inputStream.close(); // 构造可访问的URL (根据实际情况调整URL格式) - String fileUrl = "http://" + host + ":8488/riverFiles/" + module + "/" + datePath + "/" + file.getOriginalFilename(); + String fileUrl = "http://" + host + ":9010/riverFiles/" + module + "/" + datePath + "/" + file.getOriginalFilename(); +// String fileUrl = "http://" + "www.jzhd.org.cn/uploads/" + module + "/" + datePath + "/" + file.getOriginalFilename(); return fileUrl; @@ -98,6 +99,90 @@ public class FileUploadUtil { ); } + /** + * 通过SFTP上传InputStream到远程服务器(用于异步上传、PDF转图片等场景) + * + * @param host 远程服务器地址 + * @param port SSH端口 + * @param username 用户名 + * @param password 密码 + * @param remoteBasePath 远程服务器基础路径 + * @param inputStream 输入流 + * @param fileName 文件名 + * @param module 模块名称(用于创建子目录) + * @return 文件访问URL + * @throws Exception 上传异常 + */ + public static String uploadStreamToRemoteServer(String host, int port, String username, + String password, String remoteBasePath, + InputStream inputStream, String fileName, String module) throws Exception { + JSch jsch = new JSch(); + Session session = null; + ChannelSftp sftpChannel = null; + + try { + // 建立SSH连接 + session = jsch.getSession(username, host, port); + session.setPassword(password); + session.setConfig("StrictHostKeyChecking", "no"); + session.connect(30000); // 30秒超时 + + // 打开SFTP通道 + sftpChannel = (ChannelSftp) session.openChannel("sftp"); + sftpChannel.connect(30000); // 30秒超时 + + // 生成远程文件路径 + String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); + String remoteDir = remoteBasePath + module + "/" + datePath; + String remoteFilePath = remoteDir + "/" + fileName; + + // 创建远程目录(递归创建) + createRemoteDirs(sftpChannel, remoteDir); + + // 上传文件 + sftpChannel.put(inputStream, remoteFilePath); + inputStream.close(); + + // 构造可访问的URL + String fileUrl = "http://" + host + ":9010/riverFiles/" + module + "/" + datePath + "/" + fileName; + + return fileUrl; + + } finally { + // 关闭连接 + if (sftpChannel != null && sftpChannel.isConnected()) { + sftpChannel.disconnect(); + } + if (session != null && session.isConnected()) { + session.disconnect(); + } + } + } + + /** + * 通过SFTP上传InputStream到远程服务器(使用配置类) + * + * @param sftpConfig SFTP配置信息 + * @param inputStream 输入流 + * @param fileName 文件名 + * @param module 模块名称(用于创建子目录) + * @return 文件访问URL + * @throws Exception 上传异常 + */ + public static String uploadStreamToRemoteServer(SftpConfig sftpConfig, + InputStream inputStream, String fileName, String module) throws Exception { + return uploadStreamToRemoteServer( + sftpConfig.getHost(), + sftpConfig.getPort(), + sftpConfig.getUsername(), + sftpConfig.getPassword(), + sftpConfig.getPath(), + inputStream, + fileName, + module + ); + } + /** * 递归创建远程目录 * diff --git a/ruoyi-admin/src/main/java/com/ruoyi/util/InputStreamMultipartFile.java b/ruoyi-admin/src/main/java/com/ruoyi/util/InputStreamMultipartFile.java new file mode 100644 index 0000000000000000000000000000000000000000..e4de52de19a64686a7ff831f39f341c823ac4a3f --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/util/InputStreamMultipartFile.java @@ -0,0 +1,48 @@ +package com.ruoyi.util; + +import org.springframework.web.multipart.MultipartFile; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class InputStreamMultipartFile implements MultipartFile { + private final String name; + private final String originalFilename; + private final String contentType; + private final byte[] content; + + public InputStreamMultipartFile(String name, String originalFilename, String contentType, byte[] content) { + this.name = name; + this.originalFilename = originalFilename; + this.contentType = contentType; + this.content = content; + } + + @Override + public String getName() { return name; } + + @Override + public String getOriginalFilename() { return originalFilename; } + + @Override + public String getContentType() { return contentType; } + + @Override + public boolean isEmpty() { return content == null || content.length == 0; } + + @Override + public long getSize() { return content.length; } + + @Override + public byte[] getBytes() throws IOException { return content; } + + @Override + public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(content); } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + // 如需实现文件转存,可添加具体逻辑 + throw new UnsupportedOperationException("transferTo is not supported"); + } +} diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml index 44df067274cb35072c269d02e4983831e3162759..4ae24ad6f5f6776b3ce02f2af9d0fe038f91003e 100644 --- a/ruoyi-admin/src/main/resources/application-druid.yml +++ b/ruoyi-admin/src/main/resources/application-druid.yml @@ -6,7 +6,8 @@ spring: druid: # 主库数据源 master: - url: jdbc:mysql://61.184.35.153:8406/river?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + url: jdbc:mysql://127.0.0.1:3306/river?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8 +# url: jdbc:mysql://61.184.35.153:3306/river?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8 username: river password: zh241021 # url: jdbc:mysql://120.26.114.119:3306/river?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 @@ -61,4 +62,4 @@ spring: merge-sql: true wall: config: - multi-statement-allow: true \ No newline at end of file + multi-statement-allow: true diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 7e2c8c3cff73154313f79990568838e2a0535b7b..93dda7336de367f83159cf5a08129c3563a766e4 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -8,7 +8,7 @@ ruoyi: copyrightYear: 2024 # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) # profile: D:/ruoyi/uploadPath - profile: /home/ruoyi/uploadPath + profile: /data/uploadPath # 获取ip地址开关 addressEnabled: false # 验证码类型 math 数字计算 char 字符验证 @@ -58,7 +58,7 @@ spring: servlet: multipart: # 单个文件大小 - max-file-size: 300MB + max-file-size: 500MB # 设置总上传的文件大小 max-request-size: 500MB # 服务模块 @@ -69,9 +69,10 @@ spring: # redis 配置 redis: # 地址 - #host: 192.168.2.210 - host: localhost +# host: 61.184.35.153 + host: 127.0.0.1 # 端口,默认为6379 +# port: 8488 port: 6379 # 数据库索引 database: 0 @@ -137,28 +138,14 @@ xss: # 匹配链接 urlPatterns: /system/*,/monitor/*,/tool/* - -# 文件上传到oss -dromara: - x-file-storage: #文件存储配置 - default-platform: aliyun-oss-1 #默认使用的存储平台 - thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 - #对应平台的配置写在这里,注意缩进要对齐 - aliyun-oss: - - platform: aliyun-oss-1 # 存储平台标识 - enable-storage: true # 启用存储 - access-key: LTAI5tAbDqrzY6kRdn9BHtX8 #LTAI5t6q1MCr6m4NQKXZjCHF - secret-key: 785Mdh2KBvyulfkPM2GrbaKTbrH3Zk #N43GluhrEc6oeeBoJYx6zC9UyF9Ih2 - end-point: oss-cn-hangzhou.aliyuncs.com - bucket-name: web-ya-40 - domain: https://web-ya-40.oss-cn-hangzhou.aliyuncs.com/ # 访问域名,注意“/”结尾,例如:https://abc.oss-cn-shanghai.aliyuncs.com/ - base-path: river/filePath/ # 基础路径 - +Partyclass: + path:/data/project #文件上传到远程服务器 -remote-server: - sftp: - host: 61.184.35.153 - port: 8442 - username: root - password: Cjdx@!testServer2#8 - path: /home/riverFiles/ +#remote-server: +# sftp: +# host: 61.184.35.153 +# port: 8442 +# username: root +# password: Cjdx@!testServer2#8 +# path: /home/riverFiles/ +## path: /jzhddisk/project/shiJuGuanWang/uploads diff --git a/ruoyi-admin/src/main/resources/db/migration/V20250903_1530__create_column_permission_table.sql b/ruoyi-admin/src/main/resources/db/migration/V20250903_1530__create_column_permission_table.sql new file mode 100644 index 0000000000000000000000000000000000000000..1b4e68b4a3dfe10ca1b28a56b7a7adcbe78a5050 --- /dev/null +++ b/ruoyi-admin/src/main/resources/db/migration/V20250903_1530__create_column_permission_table.sql @@ -0,0 +1,34 @@ +/* + Navicat Premium Data Transfer + + Source Server : localhost + Source Server Type : MySQL + Source Server Version : 80024 + Source Host : localhost:3306 + Source Schema : river + + Target Server Type : MySQL + Target Server Version : 80024 + File Encoding : 65001 + + Date: 03/09/2025 15:30:00 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for column_permission +-- ---------------------------- +DROP TABLE IF EXISTS `column_permission`; +CREATE TABLE `column_permission` ( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `column_id` bigint(0) NOT NULL COMMENT '栏目ID', + `dept_id` bigint(0) NOT NULL COMMENT '部门ID', + `dept_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '部门名称', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '栏目临时权限' ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/logback.xml b/ruoyi-admin/src/main/resources/logback.xml index a360583fa4424fe5b8b88b39eca0a5aa203e7064..553e06759c75836b422fc64b44c6690de9855070 100644 --- a/ruoyi-admin/src/main/resources/logback.xml +++ b/ruoyi-admin/src/main/resources/logback.xml @@ -1,7 +1,7 @@ - + diff --git a/ruoyi-admin/src/main/resources/mapper/administration/AdministrationMapper.xml b/ruoyi-admin/src/main/resources/mapper/administration/AdministrationMapper.xml index 7e0a42a0718666182c6d84233525f220839b4bab..104aab00acc2a8f5ba565e44a3b9f8c9595ed990 100644 --- a/ruoyi-admin/src/main/resources/mapper/administration/AdministrationMapper.xml +++ b/ruoyi-admin/src/main/resources/mapper/administration/AdministrationMapper.xml @@ -31,7 +31,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND (k.kind LIKE CONCAT('%', #{kind}, '%') OR a.kind = #{kind}) - order by a.id desc + order by a.pubdate desc @@ -106,8 +110,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" little_title1, little_title2, attachment, - - + pdf_image_paths, + source, + #{title}, #{createTime}, @@ -124,7 +129,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{littleTitle1}, #{littleTitle2}, #{attachment}, - + #{pdfImagePaths}, + #{source}, + @@ -142,9 +149,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" appval=#{appval}, cover_path=#{coverPath}, little_title1=#{littleTitle1}, + source=#{source}, little_title2=#{littleTitle2}, attachment=#{attachment}, - pdf_image_paths= #{pdfImagePaths}, + pdf_image_paths= #{pdfImagePaths}, where article_id = #{articleId} @@ -161,8 +169,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" remark=#{remark}, ip=#{ip}, pubdate=#{pubdate}, + source= #{source}, appval=#{appval}, attachment= #{attachment}, + pdf_image_paths= #{pdfImagePaths}, cover_path=#{coverPath}, little_title1=#{littleTitle1}, little_title2=#{littleTitle2}, @@ -207,4 +217,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + diff --git a/ruoyi-admin/src/main/resources/mapper/chartColumn/ChartColumnMapper.xml b/ruoyi-admin/src/main/resources/mapper/chartColumn/ChartColumnMapper.xml new file mode 100644 index 0000000000000000000000000000000000000000..62f8fdcb69a901d07ce71903c19a88305b269ba9 --- /dev/null +++ b/ruoyi-admin/src/main/resources/mapper/chartColumn/ChartColumnMapper.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + select id, name, create_by, create_time, update_time, update_by, remark, status from chart_column + + + + + + + + insert into chart_column + + name, + create_by, + create_time, + update_time, + update_by, + remark, + status, + + + #{name}, + #{createBy}, + #{createTime}, + #{updateTime}, + #{updateBy}, + #{remark}, + #{status}, + + + + + update chart_column + + name = #{name}, + create_by = #{createBy}, + create_time = #{createTime}, + update_time = #{updateTime}, + update_by = #{updateBy}, + remark = #{remark}, + status = #{status}, + + where id = #{id} + + + + delete from chart_column where id = #{id} + + + + delete from chart_column where id in + + #{id} + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/mapper/draft/DraftArticleMapper.xml b/ruoyi-admin/src/main/resources/mapper/draft/DraftArticleMapper.xml index dcadf5761839ca97e343624a19fdb0d4e57f747e..df47ae218224a1ae09c900fe8b819ee9a81dec54 100644 --- a/ruoyi-admin/src/main/resources/mapper/draft/DraftArticleMapper.xml +++ b/ruoyi-admin/src/main/resources/mapper/draft/DraftArticleMapper.xml @@ -71,7 +71,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" create_time, update_by, update_time, - attachment, + attachment, #{title}, @@ -92,7 +92,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{createTime}, #{updateBy}, #{updateTime}, - #{attachment}, + #{attachment}, diff --git a/ruoyi-admin/src/main/resources/mapper/draft/UncheckDraftMapper.xml b/ruoyi-admin/src/main/resources/mapper/draft/UncheckDraftMapper.xml index e8e558ce91856d370e8299ef90cba4bc69917648..99775bbc847586afadbfdb36a93122bea9089385 100644 --- a/ruoyi-admin/src/main/resources/mapper/draft/UncheckDraftMapper.xml +++ b/ruoyi-admin/src/main/resources/mapper/draft/UncheckDraftMapper.xml @@ -26,6 +26,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -45,6 +46,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and pubdate <= #{endPubdate} and status = #{status} and create_by = #{createBy} + and source = #{source} order by article_id desc @@ -77,6 +79,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" update_by, update_time, attachment, + source, #{title}, @@ -99,6 +102,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{updateBy}, #{updateTime}, #{attachment}, + #{source}, @@ -125,6 +129,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" create_time = #{createTime}, update_by = #{updateBy}, update_time = #{updateTime}, + source = #{source}, where article_id = #{articleId} diff --git a/ruoyi-admin/src/main/resources/mapper/partyclass/PartyclassMapper.xml b/ruoyi-admin/src/main/resources/mapper/partyclass/PartyclassMapper.xml index 92304a08b828a86ee2b653c173856c73c492b7c0..9999657328e661ce6cb8046ae3eea0ecb64638f5 100644 --- a/ruoyi-admin/src/main/resources/mapper/partyclass/PartyclassMapper.xml +++ b/ruoyi-admin/src/main/resources/mapper/partyclass/PartyclassMapper.xml @@ -17,10 +17,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + - select id, name, file, cover_img, x, y, pubdate,author,video_origin,type,sort_order,publish from partyclass + select id, name, file, cover_img, x, y, pubdate,author,video_origin,type,sort_order,publish,user_id from partyclass WHERE type = #{param1} AND sort_order < #{param2} - ORDER BY sort_order DESC + ORDER BY sort_order DESC LIMIT 1 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/mapper/river_news/RiverNewsMapper.xml b/ruoyi-admin/src/main/resources/mapper/river_news/RiverNewsMapper.xml index b7e1e986f778e6d9d3bab280aaa99276faa7f367..dd22b62685a09918efc743b1cf79a559d65b3071 100644 --- a/ruoyi-admin/src/main/resources/mapper/river_news/RiverNewsMapper.xml +++ b/ruoyi-admin/src/main/resources/mapper/river_news/RiverNewsMapper.xml @@ -20,10 +20,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + - select news_id, title, article_origin, column_id, content, author, create_time, create_by, update_time, update_by, remark, cover_path, pubdate, little_title1, little_title2, status, sort_order from river_news + select news_id, title, article_origin, column_id, content, author, create_time, create_by, update_time, update_by, remark, cover_path, pubdate, little_title1, little_title2, status, sort_order,source from river_news where status = 0 - order by COALESCE(sort_order, 0) asc, pubdate desc + order by COALESCE(sort_order, 0) desc, pubdate desc limit 8 @@ -72,6 +74,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" little_title2, status, sort_order, + source, #{title}, @@ -90,6 +93,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{littleTitle2}, #{status}, #{sortOrder}, + #{source}, @@ -112,6 +116,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" little_title2, status, sort_order, + source, #{title}, @@ -130,6 +135,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{littleTitle2}, #{status}, #{sortOrder}, + #{source}, @@ -152,6 +158,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" little_title2 = #{littleTitle2}, status = #{status}, sort_order = #{sortOrder}, + source = #{source}, where news_id = #{newsId} @@ -175,6 +182,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" little_title2 = #{littleTitle2}, status = #{status}, sort_order = #{sortOrder}, + source = #{source}, where news_id = #{newsId} @@ -212,7 +220,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" diff --git a/ruoyi-admin/src/main/resources/mapper/station/RecoverStationMapper.xml b/ruoyi-admin/src/main/resources/mapper/station/RecoverStationMapper.xml index 38cb7998bc1198cf08657adda9c67312069957e4..bec7f81e780d434db0b7f51bc193415939766155 100644 --- a/ruoyi-admin/src/main/resources/mapper/station/RecoverStationMapper.xml +++ b/ruoyi-admin/src/main/resources/mapper/station/RecoverStationMapper.xml @@ -26,10 +26,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + - select id, title, article_origin, column_id, content, author, create_time, create_by, update_time, update_by, remark, cover_path, ip, pubdate, appval, auditId, articleId, uncheckId, state, type, valid_date from recover_station + select * from recover_station @@ -31,6 +32,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and column_id = #{columnId} and content like concat('%',#{content},'%') and author like concat('%',#{author},'%') + and source like concat('%',#{source},'%') @@ -55,6 +57,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" cover_path, little_title1, little_title2, + source, #{title}, @@ -70,6 +73,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{coverPath}, #{littleTitle1}, #{littleTitle2}, + #{source}, @@ -89,6 +93,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" cover_path, little_title1, little_title2, + source, #{title}, @@ -104,6 +109,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{coverPath}, #{littleTitle1}, #{littleTitle2}, + #{source}, @@ -123,6 +129,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" cover_path = #{coverPath}, little_title1 = #{littleTitle1}, little_title2 = #{littleTitle2}, + source = #{source}, where id = #{id} @@ -143,6 +150,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" cover_path = #{coverPath}, little_title1 = #{littleTitle1}, little_title2 = #{littleTitle2}, + source = #{source}, where id= #{id} diff --git a/ruoyi-admin/src/main/resources/mapper/uncheck/ArticleUncheckMapper.xml b/ruoyi-admin/src/main/resources/mapper/uncheck/ArticleUncheckMapper.xml index 4ca817a58cbabdd258e3b3e701338f0b244436ad..17f919214684a048b734a3196b8bbf60855d4291 100644 --- a/ruoyi-admin/src/main/resources/mapper/uncheck/ArticleUncheckMapper.xml +++ b/ruoyi-admin/src/main/resources/mapper/uncheck/ArticleUncheckMapper.xml @@ -22,7 +22,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + @@ -40,6 +40,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and state = #{state} and pubdate >= #{beginPubdate} and pubdate <= #{endPubdate} + and source like concat('%',#{source},'%') ORDER BY article_id DESC @@ -111,6 +112,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND a.content LIKE CONCAT('%', #{articleUncheck.content}, '%') + + AND a.source LIKE CONCAT('%', #{articleUncheck.source}, '%') + AND c.role IN ('1','2','3') @@ -142,7 +146,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ) - ORDER BY a.article_id DESC + ORDER BY + CASE WHEN a.state = '0' THEN 0 ELSE 1 END, + a.update_time DESC, + a.article_id DESC @@ -217,6 +239,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" little_title1, little_title2, attachment, + source, #{title}, @@ -237,7 +260,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{littleTitle1}, #{littleTitle2}, #{attachment}, - + #{source}, @@ -259,6 +282,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" pubdate = null, + pubdate = #{pubdate}, @@ -266,6 +290,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" appval = #{appval}, little_title1 = #{littleTitle1}, little_title2 = #{littleTitle2}, + source = #{source}, attachment = #{attachment}, where article_id = #{articleId} diff --git a/ruoyi-admin/src/main/resources/mapper/videoRecoverStation/VideoRecoverStationMapper.xml b/ruoyi-admin/src/main/resources/mapper/videoRecoverStation/VideoRecoverStationMapper.xml index eeea02b0b9a9628b0e78d53d141e7b18e91c9e51..c652e87385c613c75b2bd0e0332011ceb0423254 100644 --- a/ruoyi-admin/src/main/resources/mapper/videoRecoverStation/VideoRecoverStationMapper.xml +++ b/ruoyi-admin/src/main/resources/mapper/videoRecoverStation/VideoRecoverStationMapper.xml @@ -18,11 +18,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + - select id, name, file, pubdate, author, video_origin, type, cover_img, x, y, sort_order, video_id , valid_date from videoRecoverStation + select id, name, file, pubdate, author, video_origin, type, cover_img, x, y, sort_order, video_id , valid_date , user_id from videoRecoverStation @@ -55,7 +56,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" sort_order, video_id, valid_date, - + user_id, #{name}, @@ -70,7 +71,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{sortOrder}, #{videoId}, #{validDate}, - + #{userId}, @@ -88,7 +89,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" y = #{y}, sort_order = #{sortOrder}, video_id = #{videoId}, - + user_id = #{userId}, where id = #{id} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java index 511842b89f44dfa3e66d6b531de0e597bd41c6ce..8aafa0d5aed23745ebf6a13e09bad13e653cccff 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -116,6 +116,7 @@ public class SecurityConfig .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() // 除上面外的所有请求全部需要鉴权认证 + .antMatchers("/riverFiles/**").permitAll() .anyRequest().authenticated(); }) // 添加Logout filter diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java index b5b7de31659f588674b5acc9d6c5b6d6d97e1121..1840829cc562e8a2777c097ceeb0e61cea743662 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java @@ -27,6 +27,8 @@ public class ServerConfig { StringBuffer url = request.getRequestURL(); String contextPath = request.getServletContext().getContextPath(); - return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString(); + String pathUrl = url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString(); + pathUrl=pathUrl.replaceAll("122.189.98.134","www.jzhd.org.cn"); + return pathUrl; } } diff --git a/ruoyi-ui/.env.development b/ruoyi-ui/.env.development index fb58019dfd00ae127e2d74cb5723cf79abf98958..d989766d38d97182b02352859d14f93ea95162cf 100644 --- a/ruoyi-ui/.env.development +++ b/ruoyi-ui/.env.development @@ -1,5 +1,5 @@ # 页面标题 -VUE_APP_TITLE = 信息管理系统 +VUE_APP_TITLE = 河道后台管理系统 # 开发环境配置 ENV = 'development' diff --git a/ruoyi-ui/.env.production b/ruoyi-ui/.env.production index b4893b0d9eb70e7c6d0fd7792916f6f2b0a3de03..d71903b18075a82e25712d7da9c56833de6788eb 100644 --- a/ruoyi-ui/.env.production +++ b/ruoyi-ui/.env.production @@ -1,8 +1,9 @@ # 页面标题 -VUE_APP_TITLE = 若依管理系统 +VUE_APP_TITLE = 河道后台管理系统 # 生产环境配置 ENV = 'production' # 若依管理系统/生产环境 -VUE_APP_BASE_API = '/prod-api' +#VUE_APP_BASE_API = '/prod-api' +VUE_APP_BASE_API = 'https://www.jzhd.org.cn/api' \ No newline at end of file diff --git a/ruoyi-ui/.env.staging b/ruoyi-ui/.env.staging index 209b64e3a13996617643f6a5c3007e766ebadc6f..9ba148e467245ec201834247a739f02026a66b16 100644 --- a/ruoyi-ui/.env.staging +++ b/ruoyi-ui/.env.staging @@ -1,5 +1,5 @@ # 页面标题 -VUE_APP_TITLE = 若依管理系统 +VUE_APP_TITLE = 河道后台管理系统 BABEL_ENV = production diff --git a/ruoyi-ui/package.json b/ruoyi-ui/package.json index b6165173a6677343eb6b8d3235ee42cb8a8843ca..22fde62f411e9f646b3d498cfb436fd7c6d79c5a 100644 --- a/ruoyi-ui/package.json +++ b/ruoyi-ui/package.json @@ -1,7 +1,7 @@ { "name": "ruoyi", "version": "3.8.8", - "description": "信息管理系统", + "description": "河道后台管理系统", "author": "若依", "license": "MIT", "scripts": { diff --git a/ruoyi-ui/public/preview/preview.html b/ruoyi-ui/public/preview/preview.html new file mode 100644 index 0000000000000000000000000000000000000000..22356c48df00672ad5a6987a81676cceb7550757 --- /dev/null +++ b/ruoyi-ui/public/preview/preview.html @@ -0,0 +1,29262 @@ + + + + + + + + + + + + 河道管理局 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
当前位置: + + + +
+
+

+ +
+

来源:

+

发布时间:

+

责任编辑:

+
+
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
版权所有:湖北省荆州市长江河道管理局
+
地址:湖北省荆州市沙市区金龙路49号
+ +
+
+
鄂ICP备18015618号
+
技术支持:荆州市河道网络与信息化中心
+
+
+
+
+
+ + + + + + + + diff --git a/ruoyi-ui/public/preview/preview_files/000.png b/ruoyi-ui/public/preview/preview_files/000.png new file mode 100644 index 0000000000000000000000000000000000000000..2801e617c409aab15a671bb8b5776cda60a95466 Binary files /dev/null and b/ruoyi-ui/public/preview/preview_files/000.png differ diff --git a/ruoyi-ui/public/preview/preview_files/client b/ruoyi-ui/public/preview/preview_files/client new file mode 100644 index 0000000000000000000000000000000000000000..7bed1e9a21c0f18cd9dade0ed6015d72998635ae --- /dev/null +++ b/ruoyi-ui/public/preview/preview_files/client @@ -0,0 +1,831 @@ +import "/node_modules/vite/dist/client/env.mjs"; + +class HMRContext { + constructor(hmrClient, ownerPath) { + this.hmrClient = hmrClient; + this.ownerPath = ownerPath; + if (!hmrClient.dataMap.has(ownerPath)) { + hmrClient.dataMap.set(ownerPath, {}); + } + const mod = hmrClient.hotModulesMap.get(ownerPath); + if (mod) { + mod.callbacks = []; + } + const staleListeners = hmrClient.ctxToListenersMap.get(ownerPath); + if (staleListeners) { + for (const [event, staleFns] of staleListeners) { + const listeners = hmrClient.customListenersMap.get(event); + if (listeners) { + hmrClient.customListenersMap.set( + event, + listeners.filter((l) => !staleFns.includes(l)) + ); + } + } + } + this.newListeners = /* @__PURE__ */ new Map(); + hmrClient.ctxToListenersMap.set(ownerPath, this.newListeners); + } + get data() { + return this.hmrClient.dataMap.get(this.ownerPath); + } + accept(deps, callback) { + if (typeof deps === "function" || !deps) { + this.acceptDeps([this.ownerPath], ([mod]) => deps?.(mod)); + } else if (typeof deps === "string") { + this.acceptDeps([deps], ([mod]) => callback?.(mod)); + } else if (Array.isArray(deps)) { + this.acceptDeps(deps, callback); + } else { + throw new Error(`invalid hot.accept() usage.`); + } + } + // export names (first arg) are irrelevant on the client side, they're + // extracted in the server for propagation + acceptExports(_, callback) { + this.acceptDeps([this.ownerPath], ([mod]) => callback?.(mod)); + } + dispose(cb) { + this.hmrClient.disposeMap.set(this.ownerPath, cb); + } + prune(cb) { + this.hmrClient.pruneMap.set(this.ownerPath, cb); + } + // Kept for backward compatibility (#11036) + // eslint-disable-next-line @typescript-eslint/no-empty-function + decline() { + } + invalidate(message) { + this.hmrClient.notifyListeners("vite:invalidate", { + path: this.ownerPath, + message + }); + this.send("vite:invalidate", { path: this.ownerPath, message }); + this.hmrClient.logger.debug( + `[vite] invalidate ${this.ownerPath}${message ? `: ${message}` : ""}` + ); + } + on(event, cb) { + const addToMap = (map) => { + const existing = map.get(event) || []; + existing.push(cb); + map.set(event, existing); + }; + addToMap(this.hmrClient.customListenersMap); + addToMap(this.newListeners); + } + off(event, cb) { + const removeFromMap = (map) => { + const existing = map.get(event); + if (existing === void 0) { + return; + } + const pruned = existing.filter((l) => l !== cb); + if (pruned.length === 0) { + map.delete(event); + return; + } + map.set(event, pruned); + }; + removeFromMap(this.hmrClient.customListenersMap); + removeFromMap(this.newListeners); + } + send(event, data) { + this.hmrClient.messenger.send( + JSON.stringify({ type: "custom", event, data }) + ); + } + acceptDeps(deps, callback = () => { + }) { + const mod = this.hmrClient.hotModulesMap.get(this.ownerPath) || { + id: this.ownerPath, + callbacks: [] + }; + mod.callbacks.push({ + deps, + fn: callback + }); + this.hmrClient.hotModulesMap.set(this.ownerPath, mod); + } +} +class HMRMessenger { + constructor(connection) { + this.connection = connection; + this.queue = []; + } + send(message) { + this.queue.push(message); + this.flush(); + } + flush() { + if (this.connection.isReady()) { + this.queue.forEach((msg) => this.connection.send(msg)); + this.queue = []; + } + } +} +class HMRClient { + constructor(logger, connection, importUpdatedModule) { + this.logger = logger; + this.importUpdatedModule = importUpdatedModule; + this.hotModulesMap = /* @__PURE__ */ new Map(); + this.disposeMap = /* @__PURE__ */ new Map(); + this.pruneMap = /* @__PURE__ */ new Map(); + this.dataMap = /* @__PURE__ */ new Map(); + this.customListenersMap = /* @__PURE__ */ new Map(); + this.ctxToListenersMap = /* @__PURE__ */ new Map(); + this.updateQueue = []; + this.pendingUpdateQueue = false; + this.messenger = new HMRMessenger(connection); + } + async notifyListeners(event, data) { + const cbs = this.customListenersMap.get(event); + if (cbs) { + await Promise.allSettled(cbs.map((cb) => cb(data))); + } + } + clear() { + this.hotModulesMap.clear(); + this.disposeMap.clear(); + this.pruneMap.clear(); + this.dataMap.clear(); + this.customListenersMap.clear(); + this.ctxToListenersMap.clear(); + } + // After an HMR update, some modules are no longer imported on the page + // but they may have left behind side effects that need to be cleaned up + // (.e.g style injections) + async prunePaths(paths) { + await Promise.all( + paths.map((path) => { + const disposer = this.disposeMap.get(path); + if (disposer) return disposer(this.dataMap.get(path)); + }) + ); + paths.forEach((path) => { + const fn = this.pruneMap.get(path); + if (fn) { + fn(this.dataMap.get(path)); + } + }); + } + warnFailedUpdate(err, path) { + if (!err.message.includes("fetch")) { + this.logger.error(err); + } + this.logger.error( + `[hmr] Failed to reload ${path}. This could be due to syntax errors or importing non-existent modules. (see errors above)` + ); + } + /** + * buffer multiple hot updates triggered by the same src change + * so that they are invoked in the same order they were sent. + * (otherwise the order may be inconsistent because of the http request round trip) + */ + async queueUpdate(payload) { + this.updateQueue.push(this.fetchUpdate(payload)); + if (!this.pendingUpdateQueue) { + this.pendingUpdateQueue = true; + await Promise.resolve(); + this.pendingUpdateQueue = false; + const loading = [...this.updateQueue]; + this.updateQueue = []; + (await Promise.all(loading)).forEach((fn) => fn && fn()); + } + } + async fetchUpdate(update) { + const { path, acceptedPath } = update; + const mod = this.hotModulesMap.get(path); + if (!mod) { + return; + } + let fetchedModule; + const isSelfUpdate = path === acceptedPath; + const qualifiedCallbacks = mod.callbacks.filter( + ({ deps }) => deps.includes(acceptedPath) + ); + if (isSelfUpdate || qualifiedCallbacks.length > 0) { + const disposer = this.disposeMap.get(acceptedPath); + if (disposer) await disposer(this.dataMap.get(acceptedPath)); + try { + fetchedModule = await this.importUpdatedModule(update); + } catch (e) { + this.warnFailedUpdate(e, acceptedPath); + } + } + return () => { + for (const { deps, fn } of qualifiedCallbacks) { + fn( + deps.map((dep) => dep === acceptedPath ? fetchedModule : void 0) + ); + } + const loggedPath = isSelfUpdate ? path : `${acceptedPath} via ${path}`; + this.logger.debug(`[vite] hot updated: ${loggedPath}`); + }; + } +} + +const hmrConfigName = "vite.config.js"; +const base$1 = "/" || "/"; +function h(e, attrs = {}, ...children) { + const elem = document.createElement(e); + for (const [k, v] of Object.entries(attrs)) { + elem.setAttribute(k, v); + } + elem.append(...children); + return elem; +} +const templateStyle = ( + /*css*/ + ` +:host { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 99999; + --monospace: 'SFMono-Regular', Consolas, + 'Liberation Mono', Menlo, Courier, monospace; + --red: #ff5555; + --yellow: #e2aa53; + --purple: #cfa4ff; + --cyan: #2dd9da; + --dim: #c9c9c9; + + --window-background: #181818; + --window-color: #d8d8d8; +} + +.backdrop { + position: fixed; + z-index: 99999; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow-y: scroll; + margin: 0; + background: rgba(0, 0, 0, 0.66); +} + +.window { + font-family: var(--monospace); + line-height: 1.5; + max-width: 80vw; + color: var(--window-color); + box-sizing: border-box; + margin: 30px auto; + padding: 2.5vh 4vw; + position: relative; + background: var(--window-background); + border-radius: 6px 6px 8px 8px; + box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22); + overflow: hidden; + border-top: 8px solid var(--red); + direction: ltr; + text-align: left; +} + +pre { + font-family: var(--monospace); + font-size: 16px; + margin-top: 0; + margin-bottom: 1em; + overflow-x: scroll; + scrollbar-width: none; +} + +pre::-webkit-scrollbar { + display: none; +} + +pre.frame::-webkit-scrollbar { + display: block; + height: 5px; +} + +pre.frame::-webkit-scrollbar-thumb { + background: #999; + border-radius: 5px; +} + +pre.frame { + scrollbar-width: thin; +} + +.message { + line-height: 1.3; + font-weight: 600; + white-space: pre-wrap; +} + +.message-body { + color: var(--red); +} + +.plugin { + color: var(--purple); +} + +.file { + color: var(--cyan); + margin-bottom: 0; + white-space: pre-wrap; + word-break: break-all; +} + +.frame { + color: var(--yellow); +} + +.stack { + font-size: 13px; + color: var(--dim); +} + +.tip { + font-size: 13px; + color: #999; + border-top: 1px dotted #999; + padding-top: 13px; + line-height: 1.8; +} + +code { + font-size: 13px; + font-family: var(--monospace); + color: var(--yellow); +} + +.file-link { + text-decoration: underline; + cursor: pointer; +} + +kbd { + line-height: 1.5; + font-family: ui-monospace, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 0.75rem; + font-weight: 700; + background-color: rgb(38, 40, 44); + color: rgb(166, 167, 171); + padding: 0.15rem 0.3rem; + border-radius: 0.25rem; + border-width: 0.0625rem 0.0625rem 0.1875rem; + border-style: solid; + border-color: rgb(54, 57, 64); + border-image: initial; +} +` +); +const createTemplate = () => h( + "div", + { class: "backdrop", part: "backdrop" }, + h( + "div", + { class: "window", part: "window" }, + h( + "pre", + { class: "message", part: "message" }, + h("span", { class: "plugin", part: "plugin" }), + h("span", { class: "message-body", part: "message-body" }) + ), + h("pre", { class: "file", part: "file" }), + h("pre", { class: "frame", part: "frame" }), + h("pre", { class: "stack", part: "stack" }), + h( + "div", + { class: "tip", part: "tip" }, + "Click outside, press ", + h("kbd", {}, "Esc"), + " key, or fix the code to dismiss.", + h("br"), + "You can also disable this overlay by setting ", + h("code", { part: "config-option-name" }, "server.hmr.overlay"), + " to ", + h("code", { part: "config-option-value" }, "false"), + " in ", + h("code", { part: "config-file-name" }, hmrConfigName), + "." + ) + ), + h("style", {}, templateStyle) +); +const fileRE = /(?:[a-zA-Z]:\\|\/).*?:\d+:\d+/g; +const codeframeRE = /^(?:>?\s*\d+\s+\|.*|\s+\|\s*\^.*)\r?\n/gm; +const { HTMLElement = class { +} } = globalThis; +class ErrorOverlay extends HTMLElement { + constructor(err, links = true) { + super(); + this.root = this.attachShadow({ mode: "open" }); + this.root.appendChild(createTemplate()); + codeframeRE.lastIndex = 0; + const hasFrame = err.frame && codeframeRE.test(err.frame); + const message = hasFrame ? err.message.replace(codeframeRE, "") : err.message; + if (err.plugin) { + this.text(".plugin", `[plugin:${err.plugin}] `); + } + this.text(".message-body", message.trim()); + const [file] = (err.loc?.file || err.id || "unknown file").split(`?`); + if (err.loc) { + this.text(".file", `${file}:${err.loc.line}:${err.loc.column}`, links); + } else if (err.id) { + this.text(".file", file); + } + if (hasFrame) { + this.text(".frame", err.frame.trim()); + } + this.text(".stack", err.stack, links); + this.root.querySelector(".window").addEventListener("click", (e) => { + e.stopPropagation(); + }); + this.addEventListener("click", () => { + this.close(); + }); + this.closeOnEsc = (e) => { + if (e.key === "Escape" || e.code === "Escape") { + this.close(); + } + }; + document.addEventListener("keydown", this.closeOnEsc); + } + text(selector, text, linkFiles = false) { + const el = this.root.querySelector(selector); + if (!linkFiles) { + el.textContent = text; + } else { + let curIndex = 0; + let match; + fileRE.lastIndex = 0; + while (match = fileRE.exec(text)) { + const { 0: file, index } = match; + if (index != null) { + const frag = text.slice(curIndex, index); + el.appendChild(document.createTextNode(frag)); + const link = document.createElement("a"); + link.textContent = file; + link.className = "file-link"; + link.onclick = () => { + fetch( + new URL( + `${base$1}__open-in-editor?file=${encodeURIComponent(file)}`, + import.meta.url + ) + ); + }; + el.appendChild(link); + curIndex += frag.length + file.length; + } + } + } + } + close() { + this.parentNode?.removeChild(this); + document.removeEventListener("keydown", this.closeOnEsc); + } +} +const overlayId = "vite-error-overlay"; +const { customElements } = globalThis; +if (customElements && !customElements.get(overlayId)) { + customElements.define(overlayId, ErrorOverlay); +} + +console.debug("[vite] connecting..."); +const importMetaUrl = new URL(import.meta.url); +const serverHost = "localhost:8888/"; +const socketProtocol = null || (importMetaUrl.protocol === "https:" ? "wss" : "ws"); +const hmrPort = null; +const socketHost = `${null || importMetaUrl.hostname}:${hmrPort || importMetaUrl.port}${"/"}`; +const directSocketHost = "localhost:8888/"; +const base = "/" || "/"; +const wsToken = "5tbDutXxvt2a"; +let socket; +try { + let fallback; + if (!hmrPort) { + fallback = () => { + socket = setupWebSocket(socketProtocol, directSocketHost, () => { + const currentScriptHostURL = new URL(import.meta.url); + const currentScriptHost = currentScriptHostURL.host + currentScriptHostURL.pathname.replace(/@vite\/client$/, ""); + console.error( + `[vite] failed to connect to websocket. +your current setup: + (browser) ${currentScriptHost} <--[HTTP]--> ${serverHost} (server) + (browser) ${socketHost} <--[WebSocket (failing)]--> ${directSocketHost} (server) +Check out your Vite / network configuration and https://vite.dev/config/server-options.html#server-hmr .` + ); + }); + socket.addEventListener( + "open", + () => { + console.info( + "[vite] Direct websocket connection fallback. Check out https://vite.dev/config/server-options.html#server-hmr to remove the previous connection error." + ); + }, + { once: true } + ); + }; + } + socket = setupWebSocket(socketProtocol, socketHost, fallback); +} catch (error) { + console.error(`[vite] failed to connect to websocket (${error}). `); +} +function setupWebSocket(protocol, hostAndPath, onCloseWithoutOpen) { + const socket2 = new WebSocket( + `${protocol}://${hostAndPath}?token=${wsToken}`, + "vite-hmr" + ); + let isOpened = false; + socket2.addEventListener( + "open", + () => { + isOpened = true; + notifyListeners("vite:ws:connect", { webSocket: socket2 }); + }, + { once: true } + ); + socket2.addEventListener("message", async ({ data }) => { + handleMessage(JSON.parse(data)); + }); + socket2.addEventListener("close", async ({ wasClean }) => { + if (wasClean) return; + if (!isOpened && onCloseWithoutOpen) { + onCloseWithoutOpen(); + return; + } + notifyListeners("vite:ws:disconnect", { webSocket: socket2 }); + if (hasDocument) { + console.log(`[vite] server connection lost. Polling for restart...`); + await waitForSuccessfulPing(protocol, hostAndPath); + location.reload(); + } + }); + return socket2; +} +function cleanUrl(pathname) { + const url = new URL(pathname, "http://vite.dev"); + url.searchParams.delete("direct"); + return url.pathname + url.search; +} +let isFirstUpdate = true; +const outdatedLinkTags = /* @__PURE__ */ new WeakSet(); +const debounceReload = (time) => { + let timer; + return () => { + if (timer) { + clearTimeout(timer); + timer = null; + } + timer = setTimeout(() => { + location.reload(); + }, time); + }; +}; +const pageReload = debounceReload(50); +const hmrClient = new HMRClient( + console, + { + isReady: () => socket && socket.readyState === 1, + send: (message) => socket.send(message) + }, + async function importUpdatedModule({ + acceptedPath, + timestamp, + explicitImportRequired, + isWithinCircularImport + }) { + const [acceptedPathWithoutQuery, query] = acceptedPath.split(`?`); + const importPromise = import( + /* @vite-ignore */ + base + acceptedPathWithoutQuery.slice(1) + `?${explicitImportRequired ? "import&" : ""}t=${timestamp}${query ? `&${query}` : ""}` + ); + if (isWithinCircularImport) { + importPromise.catch(() => { + console.info( + `[hmr] ${acceptedPath} failed to apply HMR as it's within a circular import. Reloading page to reset the execution order. To debug and break the circular import, you can run \`vite --debug hmr\` to log the circular dependency path if a file change triggered it.` + ); + pageReload(); + }); + } + return await importPromise; + } +); +async function handleMessage(payload) { + switch (payload.type) { + case "connected": + console.debug(`[vite] connected.`); + hmrClient.messenger.flush(); + setInterval(() => { + if (socket.readyState === socket.OPEN) { + socket.send('{"type":"ping"}'); + } + }, 30000); + break; + case "update": + notifyListeners("vite:beforeUpdate", payload); + if (hasDocument) { + if (isFirstUpdate && hasErrorOverlay()) { + location.reload(); + return; + } else { + if (enableOverlay) { + clearErrorOverlay(); + } + isFirstUpdate = false; + } + } + await Promise.all( + payload.updates.map(async (update) => { + if (update.type === "js-update") { + return hmrClient.queueUpdate(update); + } + const { path, timestamp } = update; + const searchUrl = cleanUrl(path); + const el = Array.from( + document.querySelectorAll("link") + ).find( + (e) => !outdatedLinkTags.has(e) && cleanUrl(e.href).includes(searchUrl) + ); + if (!el) { + return; + } + const newPath = `${base}${searchUrl.slice(1)}${searchUrl.includes("?") ? "&" : "?"}t=${timestamp}`; + return new Promise((resolve) => { + const newLinkTag = el.cloneNode(); + newLinkTag.href = new URL(newPath, el.href).href; + const removeOldEl = () => { + el.remove(); + console.debug(`[vite] css hot updated: ${searchUrl}`); + resolve(); + }; + newLinkTag.addEventListener("load", removeOldEl); + newLinkTag.addEventListener("error", removeOldEl); + outdatedLinkTags.add(el); + el.after(newLinkTag); + }); + }) + ); + notifyListeners("vite:afterUpdate", payload); + break; + case "custom": { + notifyListeners(payload.event, payload.data); + break; + } + case "full-reload": + notifyListeners("vite:beforeFullReload", payload); + if (hasDocument) { + if (payload.path && payload.path.endsWith(".html")) { + const pagePath = decodeURI(location.pathname); + const payloadPath = base + payload.path.slice(1); + if (pagePath === payloadPath || payload.path === "/index.html" || pagePath.endsWith("/") && pagePath + "index.html" === payloadPath) { + pageReload(); + } + return; + } else { + pageReload(); + } + } + break; + case "prune": + notifyListeners("vite:beforePrune", payload); + await hmrClient.prunePaths(payload.paths); + break; + case "error": { + notifyListeners("vite:error", payload); + if (hasDocument) { + const err = payload.err; + if (enableOverlay) { + createErrorOverlay(err); + } else { + console.error( + `[vite] Internal Server Error +${err.message} +${err.stack}` + ); + } + } + break; + } + default: { + const check = payload; + return check; + } + } +} +function notifyListeners(event, data) { + hmrClient.notifyListeners(event, data); +} +const enableOverlay = true; +const hasDocument = "document" in globalThis; +function createErrorOverlay(err) { + clearErrorOverlay(); + document.body.appendChild(new ErrorOverlay(err)); +} +function clearErrorOverlay() { + document.querySelectorAll(overlayId).forEach((n) => n.close()); +} +function hasErrorOverlay() { + return document.querySelectorAll(overlayId).length; +} +async function waitForSuccessfulPing(socketProtocol2, hostAndPath, ms = 1e3) { + const pingHostProtocol = socketProtocol2 === "wss" ? "https" : "http"; + const ping = async () => { + try { + await fetch(`${pingHostProtocol}://${hostAndPath}`, { + mode: "no-cors", + headers: { + // Custom headers won't be included in a request with no-cors so (ab)use one of the + // safelisted headers to identify the ping request + Accept: "text/x-vite-ping" + } + }); + return true; + } catch { + } + return false; + }; + if (await ping()) { + return; + } + await wait(ms); + while (true) { + if (document.visibilityState === "visible") { + if (await ping()) { + break; + } + await wait(ms); + } else { + await waitForWindowShow(); + } + } +} +function wait(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} +function waitForWindowShow() { + return new Promise((resolve) => { + const onChange = async () => { + if (document.visibilityState === "visible") { + resolve(); + document.removeEventListener("visibilitychange", onChange); + } + }; + document.addEventListener("visibilitychange", onChange); + }); +} +const sheetsMap = /* @__PURE__ */ new Map(); +if ("document" in globalThis) { + document.querySelectorAll("style[data-vite-dev-id]").forEach((el) => { + sheetsMap.set(el.getAttribute("data-vite-dev-id"), el); + }); +} +const cspNonce = "document" in globalThis ? document.querySelector("meta[property=csp-nonce]")?.nonce : void 0; +let lastInsertedStyle; +function updateStyle(id, content) { + let style = sheetsMap.get(id); + if (!style) { + style = document.createElement("style"); + style.setAttribute("type", "text/css"); + style.setAttribute("data-vite-dev-id", id); + style.textContent = content; + if (cspNonce) { + style.setAttribute("nonce", cspNonce); + } + if (!lastInsertedStyle) { + document.head.appendChild(style); + setTimeout(() => { + lastInsertedStyle = void 0; + }, 0); + } else { + lastInsertedStyle.insertAdjacentElement("afterend", style); + } + lastInsertedStyle = style; + } else { + style.textContent = content; + } + sheetsMap.set(id, style); +} +function removeStyle(id) { + const style = sheetsMap.get(id); + if (style) { + document.head.removeChild(style); + sheetsMap.delete(id); + } +} +function createHotContext(ownerPath) { + return new HMRContext(hmrClient, ownerPath); +} +function injectQuery(url, queryToInject) { + if (url[0] !== "." && url[0] !== "/") { + return url; + } + const pathname = url.replace(/[?#].*$/, ""); + const { search, hash } = new URL(url, "http://vite.dev"); + return `${pathname}?${queryToInject}${search ? `&` + search.slice(1) : ""}${hash || ""}`; +} + +export { ErrorOverlay, createHotContext, injectQuery, removeStyle, updateStyle }; + +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImNsaWVudCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgXCIvbm9kZV9tb2R1bGVzL3ZpdGUvZGlzdC9jbGllbnQvZW52Lm1qc1wiO1xuXG5jbGFzcyBITVJDb250ZXh0IHtcbiAgY29uc3RydWN0b3IoaG1yQ2xpZW50LCBvd25lclBhdGgpIHtcbiAgICB0aGlzLmhtckNsaWVudCA9IGhtckNsaWVudDtcbiAgICB0aGlzLm93bmVyUGF0aCA9IG93bmVyUGF0aDtcbiAgICBpZiAoIWhtckNsaWVudC5kYXRhTWFwLmhhcyhvd25lclBhdGgpKSB7XG4gICAgICBobXJDbGllbnQuZGF0YU1hcC5zZXQob3duZXJQYXRoLCB7fSk7XG4gICAgfVxuICAgIGNvbnN0IG1vZCA9IGhtckNsaWVudC5ob3RNb2R1bGVzTWFwLmdldChvd25lclBhdGgpO1xuICAgIGlmIChtb2QpIHtcbiAgICAgIG1vZC5jYWxsYmFja3MgPSBbXTtcbiAgICB9XG4gICAgY29uc3Qgc3RhbGVMaXN0ZW5lcnMgPSBobXJDbGllbnQuY3R4VG9MaXN0ZW5lcnNNYXAuZ2V0KG93bmVyUGF0aCk7XG4gICAgaWYgKHN0YWxlTGlzdGVuZXJzKSB7XG4gICAgICBmb3IgKGNvbnN0IFtldmVudCwgc3RhbGVGbnNdIG9mIHN0YWxlTGlzdGVuZXJzKSB7XG4gICAgICAgIGNvbnN0IGxpc3RlbmVycyA9IGhtckNsaWVudC5jdXN0b21MaXN0ZW5lcnNNYXAuZ2V0KGV2ZW50KTtcbiAgICAgICAgaWYgKGxpc3RlbmVycykge1xuICAgICAgICAgIGhtckNsaWVudC5jdXN0b21MaXN0ZW5lcnNNYXAuc2V0KFxuICAgICAgICAgICAgZXZlbnQsXG4gICAgICAgICAgICBsaXN0ZW5lcnMuZmlsdGVyKChsKSA9PiAhc3RhbGVGbnMuaW5jbHVkZXMobCkpXG4gICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICB0aGlzLm5ld0xpc3RlbmVycyA9IC8qIEBfX1BVUkVfXyAqLyBuZXcgTWFwKCk7XG4gICAgaG1yQ2xpZW50LmN0eFRvTGlzdGVuZXJzTWFwLnNldChvd25lclBhdGgsIHRoaXMubmV3TGlzdGVuZXJzKTtcbiAgfVxuICBnZXQgZGF0YSgpIHtcbiAgICByZXR1cm4gdGhpcy5obXJDbGllbnQuZGF0YU1hcC5nZXQodGhpcy5vd25lclBhdGgpO1xuICB9XG4gIGFjY2VwdChkZXBzLCBjYWxsYmFjaykge1xuICAgIGlmICh0eXBlb2YgZGVwcyA9PT0gXCJmdW5jdGlvblwiIHx8ICFkZXBzKSB7XG4gICAgICB0aGlzLmFjY2VwdERlcHMoW3RoaXMub3duZXJQYXRoXSwgKFttb2RdKSA9PiBkZXBzPy4obW9kKSk7XG4gICAgfSBlbHNlIGlmICh0eXBlb2YgZGVwcyA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgdGhpcy5hY2NlcHREZXBzKFtkZXBzXSwgKFttb2RdKSA9PiBjYWxsYmFjaz8uKG1vZCkpO1xuICAgIH0gZWxzZSBpZiAoQXJyYXkuaXNBcnJheShkZXBzKSkge1xuICAgICAgdGhpcy5hY2NlcHREZXBzKGRlcHMsIGNhbGxiYWNrKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBpbnZhbGlkIGhvdC5hY2NlcHQoKSB1c2FnZS5gKTtcbiAgICB9XG4gIH1cbiAgLy8gZXhwb3J0IG5hbWVzIChmaXJzdCBhcmcpIGFyZSBpcnJlbGV2YW50IG9uIHRoZSBjbGllbnQgc2lkZSwgdGhleSdyZVxuICAvLyBleHRyYWN0ZWQgaW4gdGhlIHNlcnZlciBmb3IgcHJvcGFnYXRpb25cbiAgYWNjZXB0RXhwb3J0cyhfLCBjYWxsYmFjaykge1xuICAgIHRoaXMuYWNjZXB0RGVwcyhbdGhpcy5vd25lclBhdGhdLCAoW21vZF0pID0+IGNhbGxiYWNrPy4obW9kKSk7XG4gIH1cbiAgZGlzcG9zZShjYikge1xuICAgIHRoaXMuaG1yQ2xpZW50LmRpc3Bvc2VNYXAuc2V0KHRoaXMub3duZXJQYXRoLCBjYik7XG4gIH1cbiAgcHJ1bmUoY2IpIHtcbiAgICB0aGlzLmhtckNsaWVudC5wcnVuZU1hcC5zZXQodGhpcy5vd25lclBhdGgsIGNiKTtcbiAgfVxuICAvLyBLZXB0IGZvciBiYWNrd2FyZCBjb21wYXRpYmlsaXR5ICgjMTEwMzYpXG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tZW1wdHktZnVuY3Rpb25cbiAgZGVjbGluZSgpIHtcbiAgfVxuICBpbnZhbGlkYXRlKG1lc3NhZ2UpIHtcbiAgICB0aGlzLmhtckNsaWVudC5ub3RpZnlMaXN0ZW5lcnMoXCJ2aXRlOmludmFsaWRhdGVcIiwge1xuICAgICAgcGF0aDogdGhpcy5vd25lclBhdGgsXG4gICAgICBtZXNzYWdlXG4gICAgfSk7XG4gICAgdGhpcy5zZW5kKFwidml0ZTppbnZhbGlkYXRlXCIsIHsgcGF0aDogdGhpcy5vd25lclBhdGgsIG1lc3NhZ2UgfSk7XG4gICAgdGhpcy5obXJDbGllbnQubG9nZ2VyLmRlYnVnKFxuICAgICAgYFt2aXRlXSBpbnZhbGlkYXRlICR7dGhpcy5vd25lclBhdGh9JHttZXNzYWdlID8gYDogJHttZXNzYWdlfWAgOiBcIlwifWBcbiAgICApO1xuICB9XG4gIG9uKGV2ZW50LCBjYikge1xuICAgIGNvbnN0IGFkZFRvTWFwID0gKG1hcCkgPT4ge1xuICAgICAgY29uc3QgZXhpc3RpbmcgPSBtYXAuZ2V0KGV2ZW50KSB8fCBbXTtcbiAgICAgIGV4aXN0aW5nLnB1c2goY2IpO1xuICAgICAgbWFwLnNldChldmVudCwgZXhpc3RpbmcpO1xuICAgIH07XG4gICAgYWRkVG9NYXAodGhpcy5obXJDbGllbnQuY3VzdG9tTGlzdGVuZXJzTWFwKTtcbiAgICBhZGRUb01hcCh0aGlzLm5ld0xpc3RlbmVycyk7XG4gIH1cbiAgb2ZmKGV2ZW50LCBjYikge1xuICAgIGNvbnN0IHJlbW92ZUZyb21NYXAgPSAobWFwKSA9PiB7XG4gICAgICBjb25zdCBleGlzdGluZyA9IG1hcC5nZXQoZXZlbnQpO1xuICAgICAgaWYgKGV4aXN0aW5nID09PSB2b2lkIDApIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgY29uc3QgcHJ1bmVkID0gZXhpc3RpbmcuZmlsdGVyKChsKSA9PiBsICE9PSBjYik7XG4gICAgICBpZiAocHJ1bmVkLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICBtYXAuZGVsZXRlKGV2ZW50KTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgbWFwLnNldChldmVudCwgcHJ1bmVkKTtcbiAgICB9O1xuICAgIHJlbW92ZUZyb21NYXAodGhpcy5obXJDbGllbnQuY3VzdG9tTGlzdGVuZXJzTWFwKTtcbiAgICByZW1vdmVGcm9tTWFwKHRoaXMubmV3TGlzdGVuZXJzKTtcbiAgfVxuICBzZW5kKGV2ZW50LCBkYXRhKSB7XG4gICAgdGhpcy5obXJDbGllbnQubWVzc2VuZ2VyLnNlbmQoXG4gICAgICBKU09OLnN0cmluZ2lmeSh7IHR5cGU6IFwiY3VzdG9tXCIsIGV2ZW50LCBkYXRhIH0pXG4gICAgKTtcbiAgfVxuICBhY2NlcHREZXBzKGRlcHMsIGNhbGxiYWNrID0gKCkgPT4ge1xuICB9KSB7XG4gICAgY29uc3QgbW9kID0gdGhpcy5obXJDbGllbnQuaG90TW9kdWxlc01hcC5nZXQodGhpcy5vd25lclBhdGgpIHx8IHtcbiAgICAgIGlkOiB0aGlzLm93bmVyUGF0aCxcbiAgICAgIGNhbGxiYWNrczogW11cbiAgICB9O1xuICAgIG1vZC5jYWxsYmFja3MucHVzaCh7XG4gICAgICBkZXBzLFxuICAgICAgZm46IGNhbGxiYWNrXG4gICAgfSk7XG4gICAgdGhpcy5obXJDbGllbnQuaG90TW9kdWxlc01hcC5zZXQodGhpcy5vd25lclBhdGgsIG1vZCk7XG4gIH1cbn1cbmNsYXNzIEhNUk1lc3NlbmdlciB7XG4gIGNvbnN0cnVjdG9yKGNvbm5lY3Rpb24pIHtcbiAgICB0aGlzLmNvbm5lY3Rpb24gPSBjb25uZWN0aW9uO1xuICAgIHRoaXMucXVldWUgPSBbXTtcbiAgfVxuICBzZW5kKG1lc3NhZ2UpIHtcbiAgICB0aGlzLnF1ZXVlLnB1c2gobWVzc2FnZSk7XG4gICAgdGhpcy5mbHVzaCgpO1xuICB9XG4gIGZsdXNoKCkge1xuICAgIGlmICh0aGlzLmNvbm5lY3Rpb24uaXNSZWFkeSgpKSB7XG4gICAgICB0aGlzLnF1ZXVlLmZvckVhY2goKG1zZykgPT4gdGhpcy5jb25uZWN0aW9uLnNlbmQobXNnKSk7XG4gICAgICB0aGlzLnF1ZXVlID0gW107XG4gICAgfVxuICB9XG59XG5jbGFzcyBITVJDbGllbnQge1xuICBjb25zdHJ1Y3Rvcihsb2dnZXIsIGNvbm5lY3Rpb24sIGltcG9ydFVwZGF0ZWRNb2R1bGUpIHtcbiAgICB0aGlzLmxvZ2dlciA9IGxvZ2dlcjtcbiAgICB0aGlzLmltcG9ydFVwZGF0ZWRNb2R1bGUgPSBpbXBvcnRVcGRhdGVkTW9kdWxlO1xuICAgIHRoaXMuaG90TW9kdWxlc01hcCA9IC8qIEBfX1BVUkVfXyAqLyBuZXcgTWFwKCk7XG4gICAgdGhpcy5kaXNwb3NlTWFwID0gLyogQF9fUFVSRV9fICovIG5ldyBNYXAoKTtcbiAgICB0aGlzLnBydW5lTWFwID0gLyogQF9fUFVSRV9fICovIG5ldyBNYXAoKTtcbiAgICB0aGlzLmRhdGFNYXAgPSAvKiBAX19QVVJFX18gKi8gbmV3IE1hcCgpO1xuICAgIHRoaXMuY3VzdG9tTGlzdGVuZXJzTWFwID0gLyogQF9fUFVSRV9fICovIG5ldyBNYXAoKTtcbiAgICB0aGlzLmN0eFRvTGlzdGVuZXJzTWFwID0gLyogQF9fUFVSRV9fICovIG5ldyBNYXAoKTtcbiAgICB0aGlzLnVwZGF0ZVF1ZXVlID0gW107XG4gICAgdGhpcy5wZW5kaW5nVXBkYXRlUXVldWUgPSBmYWxzZTtcbiAgICB0aGlzLm1lc3NlbmdlciA9IG5ldyBITVJNZXNzZW5nZXIoY29ubmVjdGlvbik7XG4gIH1cbiAgYXN5bmMgbm90aWZ5TGlzdGVuZXJzKGV2ZW50LCBkYXRhKSB7XG4gICAgY29uc3QgY2JzID0gdGhpcy5jdXN0b21MaXN0ZW5lcnNNYXAuZ2V0KGV2ZW50KTtcbiAgICBpZiAoY2JzKSB7XG4gICAgICBhd2FpdCBQcm9taXNlLmFsbFNldHRsZWQoY2JzLm1hcCgoY2IpID0+IGNiKGRhdGEpKSk7XG4gICAgfVxuICB9XG4gIGNsZWFyKCkge1xuICAgIHRoaXMuaG90TW9kdWxlc01hcC5jbGVhcigpO1xuICAgIHRoaXMuZGlzcG9zZU1hcC5jbGVhcigpO1xuICAgIHRoaXMucHJ1bmVNYXAuY2xlYXIoKTtcbiAgICB0aGlzLmRhdGFNYXAuY2xlYXIoKTtcbiAgICB0aGlzLmN1c3RvbUxpc3RlbmVyc01hcC5jbGVhcigpO1xuICAgIHRoaXMuY3R4VG9MaXN0ZW5lcnNNYXAuY2xlYXIoKTtcbiAgfVxuICAvLyBBZnRlciBhbiBITVIgdXBkYXRlLCBzb21lIG1vZHVsZXMgYXJlIG5vIGxvbmdlciBpbXBvcnRlZCBvbiB0aGUgcGFnZVxuICAvLyBidXQgdGhleSBtYXkgaGF2ZSBsZWZ0IGJlaGluZCBzaWRlIGVmZmVjdHMgdGhhdCBuZWVkIHRvIGJlIGNsZWFuZWQgdXBcbiAgLy8gKC5lLmcgc3R5bGUgaW5qZWN0aW9ucylcbiAgYXN5bmMgcHJ1bmVQYXRocyhwYXRocykge1xuICAgIGF3YWl0IFByb21pc2UuYWxsKFxuICAgICAgcGF0aHMubWFwKChwYXRoKSA9PiB7XG4gICAgICAgIGNvbnN0IGRpc3Bvc2VyID0gdGhpcy5kaXNwb3NlTWFwLmdldChwYXRoKTtcbiAgICAgICAgaWYgKGRpc3Bvc2VyKSByZXR1cm4gZGlzcG9zZXIodGhpcy5kYXRhTWFwLmdldChwYXRoKSk7XG4gICAgICB9KVxuICAgICk7XG4gICAgcGF0aHMuZm9yRWFjaCgocGF0aCkgPT4ge1xuICAgICAgY29uc3QgZm4gPSB0aGlzLnBydW5lTWFwLmdldChwYXRoKTtcbiAgICAgIGlmIChmbikge1xuICAgICAgICBmbih0aGlzLmRhdGFNYXAuZ2V0KHBhdGgpKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfVxuICB3YXJuRmFpbGVkVXBkYXRlKGVyciwgcGF0aCkge1xuICAgIGlmICghZXJyLm1lc3NhZ2UuaW5jbHVkZXMoXCJmZXRjaFwiKSkge1xuICAgICAgdGhpcy5sb2dnZXIuZXJyb3IoZXJyKTtcbiAgICB9XG4gICAgdGhpcy5sb2dnZXIuZXJyb3IoXG4gICAgICBgW2htcl0gRmFpbGVkIHRvIHJlbG9hZCAke3BhdGh9LiBUaGlzIGNvdWxkIGJlIGR1ZSB0byBzeW50YXggZXJyb3JzIG9yIGltcG9ydGluZyBub24tZXhpc3RlbnQgbW9kdWxlcy4gKHNlZSBlcnJvcnMgYWJvdmUpYFxuICAgICk7XG4gIH1cbiAgLyoqXG4gICAqIGJ1ZmZlciBtdWx0aXBsZSBob3QgdXBkYXRlcyB0cmlnZ2VyZWQgYnkgdGhlIHNhbWUgc3JjIGNoYW5nZVxuICAgKiBzbyB0aGF0IHRoZXkgYXJlIGludm9rZWQgaW4gdGhlIHNhbWUgb3JkZXIgdGhleSB3ZXJlIHNlbnQuXG4gICAqIChvdGhlcndpc2UgdGhlIG9yZGVyIG1heSBiZSBpbmNvbnNpc3RlbnQgYmVjYXVzZSBvZiB0aGUgaHR0cCByZXF1ZXN0IHJvdW5kIHRyaXApXG4gICAqL1xuICBhc3luYyBxdWV1ZVVwZGF0ZShwYXlsb2FkKSB7XG4gICAgdGhpcy51cGRhdGVRdWV1ZS5wdXNoKHRoaXMuZmV0Y2hVcGRhdGUocGF5bG9hZCkpO1xuICAgIGlmICghdGhpcy5wZW5kaW5nVXBkYXRlUXVldWUpIHtcbiAgICAgIHRoaXMucGVuZGluZ1VwZGF0ZVF1ZXVlID0gdHJ1ZTtcbiAgICAgIGF3YWl0IFByb21pc2UucmVzb2x2ZSgpO1xuICAgICAgdGhpcy5wZW5kaW5nVXBkYXRlUXVldWUgPSBmYWxzZTtcbiAgICAgIGNvbnN0IGxvYWRpbmcgPSBbLi4udGhpcy51cGRhdGVRdWV1ZV07XG4gICAgICB0aGlzLnVwZGF0ZVF1ZXVlID0gW107XG4gICAgICAoYXdhaXQgUHJvbWlzZS5hbGwobG9hZGluZykpLmZvckVhY2goKGZuKSA9PiBmbiAmJiBmbigpKTtcbiAgICB9XG4gIH1cbiAgYXN5bmMgZmV0Y2hVcGRhdGUodXBkYXRlKSB7XG4gICAgY29uc3QgeyBwYXRoLCBhY2NlcHRlZFBhdGggfSA9IHVwZGF0ZTtcbiAgICBjb25zdCBtb2QgPSB0aGlzLmhvdE1vZHVsZXNNYXAuZ2V0KHBhdGgpO1xuICAgIGlmICghbW9kKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGxldCBmZXRjaGVkTW9kdWxlO1xuICAgIGNvbnN0IGlzU2VsZlVwZGF0ZSA9IHBhdGggPT09IGFjY2VwdGVkUGF0aDtcbiAgICBjb25zdCBxdWFsaWZpZWRDYWxsYmFja3MgPSBtb2QuY2FsbGJhY2tzLmZpbHRlcihcbiAgICAgICh7IGRlcHMgfSkgPT4gZGVwcy5pbmNsdWRlcyhhY2NlcHRlZFBhdGgpXG4gICAgKTtcbiAgICBpZiAoaXNTZWxmVXBkYXRlIHx8IHF1YWxpZmllZENhbGxiYWNrcy5sZW5ndGggPiAwKSB7XG4gICAgICBjb25zdCBkaXNwb3NlciA9IHRoaXMuZGlzcG9zZU1hcC5nZXQoYWNjZXB0ZWRQYXRoKTtcbiAgICAgIGlmIChkaXNwb3NlcikgYXdhaXQgZGlzcG9zZXIodGhpcy5kYXRhTWFwLmdldChhY2NlcHRlZFBhdGgpKTtcbiAgICAgIHRyeSB7XG4gICAgICAgIGZldGNoZWRNb2R1bGUgPSBhd2FpdCB0aGlzLmltcG9ydFVwZGF0ZWRNb2R1bGUodXBkYXRlKTtcbiAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgdGhpcy53YXJuRmFpbGVkVXBkYXRlKGUsIGFjY2VwdGVkUGF0aCk7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiAoKSA9PiB7XG4gICAgICBmb3IgKGNvbnN0IHsgZGVwcywgZm4gfSBvZiBxdWFsaWZpZWRDYWxsYmFja3MpIHtcbiAgICAgICAgZm4oXG4gICAgICAgICAgZGVwcy5tYXAoKGRlcCkgPT4gZGVwID09PSBhY2NlcHRlZFBhdGggPyBmZXRjaGVkTW9kdWxlIDogdm9pZCAwKVxuICAgICAgICApO1xuICAgICAgfVxuICAgICAgY29uc3QgbG9nZ2VkUGF0aCA9IGlzU2VsZlVwZGF0ZSA/IHBhdGggOiBgJHthY2NlcHRlZFBhdGh9IHZpYSAke3BhdGh9YDtcbiAgICAgIHRoaXMubG9nZ2VyLmRlYnVnKGBbdml0ZV0gaG90IHVwZGF0ZWQ6ICR7bG9nZ2VkUGF0aH1gKTtcbiAgICB9O1xuICB9XG59XG5cbmNvbnN0IGhtckNvbmZpZ05hbWUgPSBcInZpdGUuY29uZmlnLmpzXCI7XG5jb25zdCBiYXNlJDEgPSBcIi9cIiB8fCBcIi9cIjtcbmZ1bmN0aW9uIGgoZSwgYXR0cnMgPSB7fSwgLi4uY2hpbGRyZW4pIHtcbiAgY29uc3QgZWxlbSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoZSk7XG4gIGZvciAoY29uc3QgW2ssIHZdIG9mIE9iamVjdC5lbnRyaWVzKGF0dHJzKSkge1xuICAgIGVsZW0uc2V0QXR0cmlidXRlKGssIHYpO1xuICB9XG4gIGVsZW0uYXBwZW5kKC4uLmNoaWxkcmVuKTtcbiAgcmV0dXJuIGVsZW07XG59XG5jb25zdCB0ZW1wbGF0ZVN0eWxlID0gKFxuICAvKmNzcyovXG4gIGBcbjpob3N0IHtcbiAgcG9zaXRpb246IGZpeGVkO1xuICB0b3A6IDA7XG4gIGxlZnQ6IDA7XG4gIHdpZHRoOiAxMDAlO1xuICBoZWlnaHQ6IDEwMCU7XG4gIHotaW5kZXg6IDk5OTk5O1xuICAtLW1vbm9zcGFjZTogJ1NGTW9uby1SZWd1bGFyJywgQ29uc29sYXMsXG4gICdMaWJlcmF0aW9uIE1vbm8nLCBNZW5sbywgQ291cmllciwgbW9ub3NwYWNlO1xuICAtLXJlZDogI2ZmNTU1NTtcbiAgLS15ZWxsb3c6ICNlMmFhNTM7XG4gIC0tcHVycGxlOiAjY2ZhNGZmO1xuICAtLWN5YW46ICMyZGQ5ZGE7XG4gIC0tZGltOiAjYzljOWM5O1xuXG4gIC0td2luZG93LWJhY2tncm91bmQ6ICMxODE4MTg7XG4gIC0td2luZG93LWNvbG9yOiAjZDhkOGQ4O1xufVxuXG4uYmFja2Ryb3Age1xuICBwb3NpdGlvbjogZml4ZWQ7XG4gIHotaW5kZXg6IDk5OTk5O1xuICB0b3A6IDA7XG4gIGxlZnQ6IDA7XG4gIHdpZHRoOiAxMDAlO1xuICBoZWlnaHQ6IDEwMCU7XG4gIG92ZXJmbG93LXk6IHNjcm9sbDtcbiAgbWFyZ2luOiAwO1xuICBiYWNrZ3JvdW5kOiByZ2JhKDAsIDAsIDAsIDAuNjYpO1xufVxuXG4ud2luZG93IHtcbiAgZm9udC1mYW1pbHk6IHZhcigtLW1vbm9zcGFjZSk7XG4gIGxpbmUtaGVpZ2h0OiAxLjU7XG4gIG1heC13aWR0aDogODB2dztcbiAgY29sb3I6IHZhcigtLXdpbmRvdy1jb2xvcik7XG4gIGJveC1zaXppbmc6IGJvcmRlci1ib3g7XG4gIG1hcmdpbjogMzBweCBhdXRvO1xuICBwYWRkaW5nOiAyLjV2aCA0dnc7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgYmFja2dyb3VuZDogdmFyKC0td2luZG93LWJhY2tncm91bmQpO1xuICBib3JkZXItcmFkaXVzOiA2cHggNnB4IDhweCA4cHg7XG4gIGJveC1zaGFkb3c6IDAgMTlweCAzOHB4IHJnYmEoMCwwLDAsMC4zMCksIDAgMTVweCAxMnB4IHJnYmEoMCwwLDAsMC4yMik7XG4gIG92ZXJmbG93OiBoaWRkZW47XG4gIGJvcmRlci10b3A6IDhweCBzb2xpZCB2YXIoLS1yZWQpO1xuICBkaXJlY3Rpb246IGx0cjtcbiAgdGV4dC1hbGlnbjogbGVmdDtcbn1cblxucHJlIHtcbiAgZm9udC1mYW1pbHk6IHZhcigtLW1vbm9zcGFjZSk7XG4gIGZvbnQtc2l6ZTogMTZweDtcbiAgbWFyZ2luLXRvcDogMDtcbiAgbWFyZ2luLWJvdHRvbTogMWVtO1xuICBvdmVyZmxvdy14OiBzY3JvbGw7XG4gIHNjcm9sbGJhci13aWR0aDogbm9uZTtcbn1cblxucHJlOjotd2Via2l0LXNjcm9sbGJhciB7XG4gIGRpc3BsYXk6IG5vbmU7XG59XG5cbnByZS5mcmFtZTo6LXdlYmtpdC1zY3JvbGxiYXIge1xuICBkaXNwbGF5OiBibG9jaztcbiAgaGVpZ2h0OiA1cHg7XG59XG5cbnByZS5mcmFtZTo6LXdlYmtpdC1zY3JvbGxiYXItdGh1bWIge1xuICBiYWNrZ3JvdW5kOiAjOTk5O1xuICBib3JkZXItcmFkaXVzOiA1cHg7XG59XG5cbnByZS5mcmFtZSB7XG4gIHNjcm9sbGJhci13aWR0aDogdGhpbjtcbn1cblxuLm1lc3NhZ2Uge1xuICBsaW5lLWhlaWdodDogMS4zO1xuICBmb250LXdlaWdodDogNjAwO1xuICB3aGl0ZS1zcGFjZTogcHJlLXdyYXA7XG59XG5cbi5tZXNzYWdlLWJvZHkge1xuICBjb2xvcjogdmFyKC0tcmVkKTtcbn1cblxuLnBsdWdpbiB7XG4gIGNvbG9yOiB2YXIoLS1wdXJwbGUpO1xufVxuXG4uZmlsZSB7XG4gIGNvbG9yOiB2YXIoLS1jeWFuKTtcbiAgbWFyZ2luLWJvdHRvbTogMDtcbiAgd2hpdGUtc3BhY2U6IHByZS13cmFwO1xuICB3b3JkLWJyZWFrOiBicmVhay1hbGw7XG59XG5cbi5mcmFtZSB7XG4gIGNvbG9yOiB2YXIoLS15ZWxsb3cpO1xufVxuXG4uc3RhY2sge1xuICBmb250LXNpemU6IDEzcHg7XG4gIGNvbG9yOiB2YXIoLS1kaW0pO1xufVxuXG4udGlwIHtcbiAgZm9udC1zaXplOiAxM3B4O1xuICBjb2xvcjogIzk5OTtcbiAgYm9yZGVyLXRvcDogMXB4IGRvdHRlZCAjOTk5O1xuICBwYWRkaW5nLXRvcDogMTNweDtcbiAgbGluZS1oZWlnaHQ6IDEuODtcbn1cblxuY29kZSB7XG4gIGZvbnQtc2l6ZTogMTNweDtcbiAgZm9udC1mYW1pbHk6IHZhcigtLW1vbm9zcGFjZSk7XG4gIGNvbG9yOiB2YXIoLS15ZWxsb3cpO1xufVxuXG4uZmlsZS1saW5rIHtcbiAgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmU7XG4gIGN1cnNvcjogcG9pbnRlcjtcbn1cblxua2JkIHtcbiAgbGluZS1oZWlnaHQ6IDEuNTtcbiAgZm9udC1mYW1pbHk6IHVpLW1vbm9zcGFjZSwgTWVubG8sIE1vbmFjbywgQ29uc29sYXMsIFwiTGliZXJhdGlvbiBNb25vXCIsIFwiQ291cmllciBOZXdcIiwgbW9ub3NwYWNlO1xuICBmb250LXNpemU6IDAuNzVyZW07XG4gIGZvbnQtd2VpZ2h0OiA3MDA7XG4gIGJhY2tncm91bmQtY29sb3I6IHJnYigzOCwgNDAsIDQ0KTtcbiAgY29sb3I6IHJnYigxNjYsIDE2NywgMTcxKTtcbiAgcGFkZGluZzogMC4xNXJlbSAwLjNyZW07XG4gIGJvcmRlci1yYWRpdXM6IDAuMjVyZW07XG4gIGJvcmRlci13aWR0aDogMC4wNjI1cmVtIDAuMDYyNXJlbSAwLjE4NzVyZW07XG4gIGJvcmRlci1zdHlsZTogc29saWQ7XG4gIGJvcmRlci1jb2xvcjogcmdiKDU0LCA1NywgNjQpO1xuICBib3JkZXItaW1hZ2U6IGluaXRpYWw7XG59XG5gXG4pO1xuY29uc3QgY3JlYXRlVGVtcGxhdGUgPSAoKSA9PiBoKFxuICBcImRpdlwiLFxuICB7IGNsYXNzOiBcImJhY2tkcm9wXCIsIHBhcnQ6IFwiYmFja2Ryb3BcIiB9LFxuICBoKFxuICAgIFwiZGl2XCIsXG4gICAgeyBjbGFzczogXCJ3aW5kb3dcIiwgcGFydDogXCJ3aW5kb3dcIiB9LFxuICAgIGgoXG4gICAgICBcInByZVwiLFxuICAgICAgeyBjbGFzczogXCJtZXNzYWdlXCIsIHBhcnQ6IFwibWVzc2FnZVwiIH0sXG4gICAgICBoKFwic3BhblwiLCB7IGNsYXNzOiBcInBsdWdpblwiLCBwYXJ0OiBcInBsdWdpblwiIH0pLFxuICAgICAgaChcInNwYW5cIiwgeyBjbGFzczogXCJtZXNzYWdlLWJvZHlcIiwgcGFydDogXCJtZXNzYWdlLWJvZHlcIiB9KVxuICAgICksXG4gICAgaChcInByZVwiLCB7IGNsYXNzOiBcImZpbGVcIiwgcGFydDogXCJmaWxlXCIgfSksXG4gICAgaChcInByZVwiLCB7IGNsYXNzOiBcImZyYW1lXCIsIHBhcnQ6IFwiZnJhbWVcIiB9KSxcbiAgICBoKFwicHJlXCIsIHsgY2xhc3M6IFwic3RhY2tcIiwgcGFydDogXCJzdGFja1wiIH0pLFxuICAgIGgoXG4gICAgICBcImRpdlwiLFxuICAgICAgeyBjbGFzczogXCJ0aXBcIiwgcGFydDogXCJ0aXBcIiB9LFxuICAgICAgXCJDbGljayBvdXRzaWRlLCBwcmVzcyBcIixcbiAgICAgIGgoXCJrYmRcIiwge30sIFwiRXNjXCIpLFxuICAgICAgXCIga2V5LCBvciBmaXggdGhlIGNvZGUgdG8gZGlzbWlzcy5cIixcbiAgICAgIGgoXCJiclwiKSxcbiAgICAgIFwiWW91IGNhbiBhbHNvIGRpc2FibGUgdGhpcyBvdmVybGF5IGJ5IHNldHRpbmcgXCIsXG4gICAgICBoKFwiY29kZVwiLCB7IHBhcnQ6IFwiY29uZmlnLW9wdGlvbi1uYW1lXCIgfSwgXCJzZXJ2ZXIuaG1yLm92ZXJsYXlcIiksXG4gICAgICBcIiB0byBcIixcbiAgICAgIGgoXCJjb2RlXCIsIHsgcGFydDogXCJjb25maWctb3B0aW9uLXZhbHVlXCIgfSwgXCJmYWxzZVwiKSxcbiAgICAgIFwiIGluIFwiLFxuICAgICAgaChcImNvZGVcIiwgeyBwYXJ0OiBcImNvbmZpZy1maWxlLW5hbWVcIiB9LCBobXJDb25maWdOYW1lKSxcbiAgICAgIFwiLlwiXG4gICAgKVxuICApLFxuICBoKFwic3R5bGVcIiwge30sIHRlbXBsYXRlU3R5bGUpXG4pO1xuY29uc3QgZmlsZVJFID0gLyg/OlthLXpBLVpdOlxcXFx8XFwvKS4qPzpcXGQrOlxcZCsvZztcbmNvbnN0IGNvZGVmcmFtZVJFID0gL14oPzo+P1xccypcXGQrXFxzK1xcfC4qfFxccytcXHxcXHMqXFxeLiopXFxyP1xcbi9nbTtcbmNvbnN0IHsgSFRNTEVsZW1lbnQgPSBjbGFzcyB7XG59IH0gPSBnbG9iYWxUaGlzO1xuY2xhc3MgRXJyb3JPdmVybGF5IGV4dGVuZHMgSFRNTEVsZW1lbnQge1xuICBjb25zdHJ1Y3RvcihlcnIsIGxpbmtzID0gdHJ1ZSkge1xuICAgIHN1cGVyKCk7XG4gICAgdGhpcy5yb290ID0gdGhpcy5hdHRhY2hTaGFkb3coeyBtb2RlOiBcIm9wZW5cIiB9KTtcbiAgICB0aGlzLnJvb3QuYXBwZW5kQ2hpbGQoY3JlYXRlVGVtcGxhdGUoKSk7XG4gICAgY29kZWZyYW1lUkUubGFzdEluZGV4ID0gMDtcbiAgICBjb25zdCBoYXNGcmFtZSA9IGVyci5mcmFtZSAmJiBjb2RlZnJhbWVSRS50ZXN0KGVyci5mcmFtZSk7XG4gICAgY29uc3QgbWVzc2FnZSA9IGhhc0ZyYW1lID8gZXJyLm1lc3NhZ2UucmVwbGFjZShjb2RlZnJhbWVSRSwgXCJcIikgOiBlcnIubWVzc2FnZTtcbiAgICBpZiAoZXJyLnBsdWdpbikge1xuICAgICAgdGhpcy50ZXh0KFwiLnBsdWdpblwiLCBgW3BsdWdpbjoke2Vyci5wbHVnaW59XSBgKTtcbiAgICB9XG4gICAgdGhpcy50ZXh0KFwiLm1lc3NhZ2UtYm9keVwiLCBtZXNzYWdlLnRyaW0oKSk7XG4gICAgY29uc3QgW2ZpbGVdID0gKGVyci5sb2M/LmZpbGUgfHwgZXJyLmlkIHx8IFwidW5rbm93biBmaWxlXCIpLnNwbGl0KGA/YCk7XG4gICAgaWYgKGVyci5sb2MpIHtcbiAgICAgIHRoaXMudGV4dChcIi5maWxlXCIsIGAke2ZpbGV9OiR7ZXJyLmxvYy5saW5lfToke2Vyci5sb2MuY29sdW1ufWAsIGxpbmtzKTtcbiAgICB9IGVsc2UgaWYgKGVyci5pZCkge1xuICAgICAgdGhpcy50ZXh0KFwiLmZpbGVcIiwgZmlsZSk7XG4gICAgfVxuICAgIGlmIChoYXNGcmFtZSkge1xuICAgICAgdGhpcy50ZXh0KFwiLmZyYW1lXCIsIGVyci5mcmFtZS50cmltKCkpO1xuICAgIH1cbiAgICB0aGlzLnRleHQoXCIuc3RhY2tcIiwgZXJyLnN0YWNrLCBsaW5rcyk7XG4gICAgdGhpcy5yb290LnF1ZXJ5U2VsZWN0b3IoXCIud2luZG93XCIpLmFkZEV2ZW50TGlzdGVuZXIoXCJjbGlja1wiLCAoZSkgPT4ge1xuICAgICAgZS5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICB9KTtcbiAgICB0aGlzLmFkZEV2ZW50TGlzdGVuZXIoXCJjbGlja1wiLCAoKSA9PiB7XG4gICAgICB0aGlzLmNsb3NlKCk7XG4gICAgfSk7XG4gICAgdGhpcy5jbG9zZU9uRXNjID0gKGUpID0+IHtcbiAgICAgIGlmIChlLmtleSA9PT0gXCJFc2NhcGVcIiB8fCBlLmNvZGUgPT09IFwiRXNjYXBlXCIpIHtcbiAgICAgICAgdGhpcy5jbG9zZSgpO1xuICAgICAgfVxuICAgIH07XG4gICAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcihcImtleWRvd25cIiwgdGhpcy5jbG9zZU9uRXNjKTtcbiAgfVxuICB0ZXh0KHNlbGVjdG9yLCB0ZXh0LCBsaW5rRmlsZXMgPSBmYWxzZSkge1xuICAgIGNvbnN0IGVsID0gdGhpcy5yb290LnF1ZXJ5U2VsZWN0b3Ioc2VsZWN0b3IpO1xuICAgIGlmICghbGlua0ZpbGVzKSB7XG4gICAgICBlbC50ZXh0Q29udGVudCA9IHRleHQ7XG4gICAgfSBlbHNlIHtcbiAgICAgIGxldCBjdXJJbmRleCA9IDA7XG4gICAgICBsZXQgbWF0Y2g7XG4gICAgICBmaWxlUkUubGFzdEluZGV4ID0gMDtcbiAgICAgIHdoaWxlIChtYXRjaCA9IGZpbGVSRS5leGVjKHRleHQpKSB7XG4gICAgICAgIGNvbnN0IHsgMDogZmlsZSwgaW5kZXggfSA9IG1hdGNoO1xuICAgICAgICBpZiAoaW5kZXggIT0gbnVsbCkge1xuICAgICAgICAgIGNvbnN0IGZyYWcgPSB0ZXh0LnNsaWNlKGN1ckluZGV4LCBpbmRleCk7XG4gICAgICAgICAgZWwuYXBwZW5kQ2hpbGQoZG9jdW1lbnQuY3JlYXRlVGV4dE5vZGUoZnJhZykpO1xuICAgICAgICAgIGNvbnN0IGxpbmsgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwiYVwiKTtcbiAgICAgICAgICBsaW5rLnRleHRDb250ZW50ID0gZmlsZTtcbiAgICAgICAgICBsaW5rLmNsYXNzTmFtZSA9IFwiZmlsZS1saW5rXCI7XG4gICAgICAgICAgbGluay5vbmNsaWNrID0gKCkgPT4ge1xuICAgICAgICAgICAgZmV0Y2goXG4gICAgICAgICAgICAgIG5ldyBVUkwoXG4gICAgICAgICAgICAgICAgYCR7YmFzZSQxfV9fb3Blbi1pbi1lZGl0b3I/ZmlsZT0ke2VuY29kZVVSSUNvbXBvbmVudChmaWxlKX1gLFxuICAgICAgICAgICAgICAgIGltcG9ydC5tZXRhLnVybFxuICAgICAgICAgICAgICApXG4gICAgICAgICAgICApO1xuICAgICAgICAgIH07XG4gICAgICAgICAgZWwuYXBwZW5kQ2hpbGQobGluayk7XG4gICAgICAgICAgY3VySW5kZXggKz0gZnJhZy5sZW5ndGggKyBmaWxlLmxlbmd0aDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuICBjbG9zZSgpIHtcbiAgICB0aGlzLnBhcmVudE5vZGU/LnJlbW92ZUNoaWxkKHRoaXMpO1xuICAgIGRvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoXCJrZXlkb3duXCIsIHRoaXMuY2xvc2VPbkVzYyk7XG4gIH1cbn1cbmNvbnN0IG92ZXJsYXlJZCA9IFwidml0ZS1lcnJvci1vdmVybGF5XCI7XG5jb25zdCB7IGN1c3RvbUVsZW1lbnRzIH0gPSBnbG9iYWxUaGlzO1xuaWYgKGN1c3RvbUVsZW1lbnRzICYmICFjdXN0b21FbGVtZW50cy5nZXQob3ZlcmxheUlkKSkge1xuICBjdXN0b21FbGVtZW50cy5kZWZpbmUob3ZlcmxheUlkLCBFcnJvck92ZXJsYXkpO1xufVxuXG5jb25zb2xlLmRlYnVnKFwiW3ZpdGVdIGNvbm5lY3RpbmcuLi5cIik7XG5jb25zdCBpbXBvcnRNZXRhVXJsID0gbmV3IFVSTChpbXBvcnQubWV0YS51cmwpO1xuY29uc3Qgc2VydmVySG9zdCA9IFwibG9jYWxob3N0Ojg4ODgvXCI7XG5jb25zdCBzb2NrZXRQcm90b2NvbCA9IG51bGwgfHwgKGltcG9ydE1ldGFVcmwucHJvdG9jb2wgPT09IFwiaHR0cHM6XCIgPyBcIndzc1wiIDogXCJ3c1wiKTtcbmNvbnN0IGhtclBvcnQgPSBudWxsO1xuY29uc3Qgc29ja2V0SG9zdCA9IGAke251bGwgfHwgaW1wb3J0TWV0YVVybC5ob3N0bmFtZX06JHtobXJQb3J0IHx8IGltcG9ydE1ldGFVcmwucG9ydH0ke1wiL1wifWA7XG5jb25zdCBkaXJlY3RTb2NrZXRIb3N0ID0gXCJsb2NhbGhvc3Q6ODg4OC9cIjtcbmNvbnN0IGJhc2UgPSBcIi9cIiB8fCBcIi9cIjtcbmNvbnN0IHdzVG9rZW4gPSBcIjV0YkR1dFh4dnQyYVwiO1xubGV0IHNvY2tldDtcbnRyeSB7XG4gIGxldCBmYWxsYmFjaztcbiAgaWYgKCFobXJQb3J0KSB7XG4gICAgZmFsbGJhY2sgPSAoKSA9PiB7XG4gICAgICBzb2NrZXQgPSBzZXR1cFdlYlNvY2tldChzb2NrZXRQcm90b2NvbCwgZGlyZWN0U29ja2V0SG9zdCwgKCkgPT4ge1xuICAgICAgICBjb25zdCBjdXJyZW50U2NyaXB0SG9zdFVSTCA9IG5ldyBVUkwoaW1wb3J0Lm1ldGEudXJsKTtcbiAgICAgICAgY29uc3QgY3VycmVudFNjcmlwdEhvc3QgPSBjdXJyZW50U2NyaXB0SG9zdFVSTC5ob3N0ICsgY3VycmVudFNjcmlwdEhvc3RVUkwucGF0aG5hbWUucmVwbGFjZSgvQHZpdGVcXC9jbGllbnQkLywgXCJcIik7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoXG4gICAgICAgICAgYFt2aXRlXSBmYWlsZWQgdG8gY29ubmVjdCB0byB3ZWJzb2NrZXQuXG55b3VyIGN1cnJlbnQgc2V0dXA6XG4gIChicm93c2VyKSAke2N1cnJlbnRTY3JpcHRIb3N0fSA8LS1bSFRUUF0tLT4gJHtzZXJ2ZXJIb3N0fSAoc2VydmVyKVxuICAoYnJvd3NlcikgJHtzb2NrZXRIb3N0fSA8LS1bV2ViU29ja2V0IChmYWlsaW5nKV0tLT4gJHtkaXJlY3RTb2NrZXRIb3N0fSAoc2VydmVyKVxuQ2hlY2sgb3V0IHlvdXIgVml0ZSAvIG5ldHdvcmsgY29uZmlndXJhdGlvbiBhbmQgaHR0cHM6Ly92aXRlLmRldi9jb25maWcvc2VydmVyLW9wdGlvbnMuaHRtbCNzZXJ2ZXItaG1yIC5gXG4gICAgICAgICk7XG4gICAgICB9KTtcbiAgICAgIHNvY2tldC5hZGRFdmVudExpc3RlbmVyKFxuICAgICAgICBcIm9wZW5cIixcbiAgICAgICAgKCkgPT4ge1xuICAgICAgICAgIGNvbnNvbGUuaW5mbyhcbiAgICAgICAgICAgIFwiW3ZpdGVdIERpcmVjdCB3ZWJzb2NrZXQgY29ubmVjdGlvbiBmYWxsYmFjay4gQ2hlY2sgb3V0IGh0dHBzOi8vdml0ZS5kZXYvY29uZmlnL3NlcnZlci1vcHRpb25zLmh0bWwjc2VydmVyLWhtciB0byByZW1vdmUgdGhlIHByZXZpb3VzIGNvbm5lY3Rpb24gZXJyb3IuXCJcbiAgICAgICAgICApO1xuICAgICAgICB9LFxuICAgICAgICB7IG9uY2U6IHRydWUgfVxuICAgICAgKTtcbiAgICB9O1xuICB9XG4gIHNvY2tldCA9IHNldHVwV2ViU29ja2V0KHNvY2tldFByb3RvY29sLCBzb2NrZXRIb3N0LCBmYWxsYmFjayk7XG59IGNhdGNoIChlcnJvcikge1xuICBjb25zb2xlLmVycm9yKGBbdml0ZV0gZmFpbGVkIHRvIGNvbm5lY3QgdG8gd2Vic29ja2V0ICgke2Vycm9yfSkuIGApO1xufVxuZnVuY3Rpb24gc2V0dXBXZWJTb2NrZXQocHJvdG9jb2wsIGhvc3RBbmRQYXRoLCBvbkNsb3NlV2l0aG91dE9wZW4pIHtcbiAgY29uc3Qgc29ja2V0MiA9IG5ldyBXZWJTb2NrZXQoXG4gICAgYCR7cHJvdG9jb2x9Oi8vJHtob3N0QW5kUGF0aH0/dG9rZW49JHt3c1Rva2VufWAsXG4gICAgXCJ2aXRlLWhtclwiXG4gICk7XG4gIGxldCBpc09wZW5lZCA9IGZhbHNlO1xuICBzb2NrZXQyLmFkZEV2ZW50TGlzdGVuZXIoXG4gICAgXCJvcGVuXCIsXG4gICAgKCkgPT4ge1xuICAgICAgaXNPcGVuZWQgPSB0cnVlO1xuICAgICAgbm90aWZ5TGlzdGVuZXJzKFwidml0ZTp3czpjb25uZWN0XCIsIHsgd2ViU29ja2V0OiBzb2NrZXQyIH0pO1xuICAgIH0sXG4gICAgeyBvbmNlOiB0cnVlIH1cbiAgKTtcbiAgc29ja2V0Mi5hZGRFdmVudExpc3RlbmVyKFwibWVzc2FnZVwiLCBhc3luYyAoeyBkYXRhIH0pID0+IHtcbiAgICBoYW5kbGVNZXNzYWdlKEpTT04ucGFyc2UoZGF0YSkpO1xuICB9KTtcbiAgc29ja2V0Mi5hZGRFdmVudExpc3RlbmVyKFwiY2xvc2VcIiwgYXN5bmMgKHsgd2FzQ2xlYW4gfSkgPT4ge1xuICAgIGlmICh3YXNDbGVhbikgcmV0dXJuO1xuICAgIGlmICghaXNPcGVuZWQgJiYgb25DbG9zZVdpdGhvdXRPcGVuKSB7XG4gICAgICBvbkNsb3NlV2l0aG91dE9wZW4oKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgbm90aWZ5TGlzdGVuZXJzKFwidml0ZTp3czpkaXNjb25uZWN0XCIsIHsgd2ViU29ja2V0OiBzb2NrZXQyIH0pO1xuICAgIGlmIChoYXNEb2N1bWVudCkge1xuICAgICAgY29uc29sZS5sb2coYFt2aXRlXSBzZXJ2ZXIgY29ubmVjdGlvbiBsb3N0LiBQb2xsaW5nIGZvciByZXN0YXJ0Li4uYCk7XG4gICAgICBhd2FpdCB3YWl0Rm9yU3VjY2Vzc2Z1bFBpbmcocHJvdG9jb2wsIGhvc3RBbmRQYXRoKTtcbiAgICAgIGxvY2F0aW9uLnJlbG9hZCgpO1xuICAgIH1cbiAgfSk7XG4gIHJldHVybiBzb2NrZXQyO1xufVxuZnVuY3Rpb24gY2xlYW5VcmwocGF0aG5hbWUpIHtcbiAgY29uc3QgdXJsID0gbmV3IFVSTChwYXRobmFtZSwgXCJodHRwOi8vdml0ZS5kZXZcIik7XG4gIHVybC5zZWFyY2hQYXJhbXMuZGVsZXRlKFwiZGlyZWN0XCIpO1xuICByZXR1cm4gdXJsLnBhdGhuYW1lICsgdXJsLnNlYXJjaDtcbn1cbmxldCBpc0ZpcnN0VXBkYXRlID0gdHJ1ZTtcbmNvbnN0IG91dGRhdGVkTGlua1RhZ3MgPSAvKiBAX19QVVJFX18gKi8gbmV3IFdlYWtTZXQoKTtcbmNvbnN0IGRlYm91bmNlUmVsb2FkID0gKHRpbWUpID0+IHtcbiAgbGV0IHRpbWVyO1xuICByZXR1cm4gKCkgPT4ge1xuICAgIGlmICh0aW1lcikge1xuICAgICAgY2xlYXJUaW1lb3V0KHRpbWVyKTtcbiAgICAgIHRpbWVyID0gbnVsbDtcbiAgICB9XG4gICAgdGltZXIgPSBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgIGxvY2F0aW9uLnJlbG9hZCgpO1xuICAgIH0sIHRpbWUpO1xuICB9O1xufTtcbmNvbnN0IHBhZ2VSZWxvYWQgPSBkZWJvdW5jZVJlbG9hZCg1MCk7XG5jb25zdCBobXJDbGllbnQgPSBuZXcgSE1SQ2xpZW50KFxuICBjb25zb2xlLFxuICB7XG4gICAgaXNSZWFkeTogKCkgPT4gc29ja2V0ICYmIHNvY2tldC5yZWFkeVN0YXRlID09PSAxLFxuICAgIHNlbmQ6IChtZXNzYWdlKSA9PiBzb2NrZXQuc2VuZChtZXNzYWdlKVxuICB9LFxuICBhc3luYyBmdW5jdGlvbiBpbXBvcnRVcGRhdGVkTW9kdWxlKHtcbiAgICBhY2NlcHRlZFBhdGgsXG4gICAgdGltZXN0YW1wLFxuICAgIGV4cGxpY2l0SW1wb3J0UmVxdWlyZWQsXG4gICAgaXNXaXRoaW5DaXJjdWxhckltcG9ydFxuICB9KSB7XG4gICAgY29uc3QgW2FjY2VwdGVkUGF0aFdpdGhvdXRRdWVyeSwgcXVlcnldID0gYWNjZXB0ZWRQYXRoLnNwbGl0KGA/YCk7XG4gICAgY29uc3QgaW1wb3J0UHJvbWlzZSA9IGltcG9ydChcbiAgICAgIC8qIEB2aXRlLWlnbm9yZSAqL1xuICAgICAgYmFzZSArIGFjY2VwdGVkUGF0aFdpdGhvdXRRdWVyeS5zbGljZSgxKSArIGA/JHtleHBsaWNpdEltcG9ydFJlcXVpcmVkID8gXCJpbXBvcnQmXCIgOiBcIlwifXQ9JHt0aW1lc3RhbXB9JHtxdWVyeSA/IGAmJHtxdWVyeX1gIDogXCJcIn1gXG4gICAgKTtcbiAgICBpZiAoaXNXaXRoaW5DaXJjdWxhckltcG9ydCkge1xuICAgICAgaW1wb3J0UHJvbWlzZS5jYXRjaCgoKSA9PiB7XG4gICAgICAgIGNvbnNvbGUuaW5mbyhcbiAgICAgICAgICBgW2htcl0gJHthY2NlcHRlZFBhdGh9IGZhaWxlZCB0byBhcHBseSBITVIgYXMgaXQncyB3aXRoaW4gYSBjaXJjdWxhciBpbXBvcnQuIFJlbG9hZGluZyBwYWdlIHRvIHJlc2V0IHRoZSBleGVjdXRpb24gb3JkZXIuIFRvIGRlYnVnIGFuZCBicmVhayB0aGUgY2lyY3VsYXIgaW1wb3J0LCB5b3UgY2FuIHJ1biBcXGB2aXRlIC0tZGVidWcgaG1yXFxgIHRvIGxvZyB0aGUgY2lyY3VsYXIgZGVwZW5kZW5jeSBwYXRoIGlmIGEgZmlsZSBjaGFuZ2UgdHJpZ2dlcmVkIGl0LmBcbiAgICAgICAgKTtcbiAgICAgICAgcGFnZVJlbG9hZCgpO1xuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiBhd2FpdCBpbXBvcnRQcm9taXNlO1xuICB9XG4pO1xuYXN5bmMgZnVuY3Rpb24gaGFuZGxlTWVzc2FnZShwYXlsb2FkKSB7XG4gIHN3aXRjaCAocGF5bG9hZC50eXBlKSB7XG4gICAgY2FzZSBcImNvbm5lY3RlZFwiOlxuICAgICAgY29uc29sZS5kZWJ1ZyhgW3ZpdGVdIGNvbm5lY3RlZC5gKTtcbiAgICAgIGhtckNsaWVudC5tZXNzZW5nZXIuZmx1c2goKTtcbiAgICAgIHNldEludGVydmFsKCgpID0+IHtcbiAgICAgICAgaWYgKHNvY2tldC5yZWFkeVN0YXRlID09PSBzb2NrZXQuT1BFTikge1xuICAgICAgICAgIHNvY2tldC5zZW5kKCd7XCJ0eXBlXCI6XCJwaW5nXCJ9Jyk7XG4gICAgICAgIH1cbiAgICAgIH0sIDMwMDAwKTtcbiAgICAgIGJyZWFrO1xuICAgIGNhc2UgXCJ1cGRhdGVcIjpcbiAgICAgIG5vdGlmeUxpc3RlbmVycyhcInZpdGU6YmVmb3JlVXBkYXRlXCIsIHBheWxvYWQpO1xuICAgICAgaWYgKGhhc0RvY3VtZW50KSB7XG4gICAgICAgIGlmIChpc0ZpcnN0VXBkYXRlICYmIGhhc0Vycm9yT3ZlcmxheSgpKSB7XG4gICAgICAgICAgbG9jYXRpb24ucmVsb2FkKCk7XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGlmIChlbmFibGVPdmVybGF5KSB7XG4gICAgICAgICAgICBjbGVhckVycm9yT3ZlcmxheSgpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBpc0ZpcnN0VXBkYXRlID0gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGF3YWl0IFByb21pc2UuYWxsKFxuICAgICAgICBwYXlsb2FkLnVwZGF0ZXMubWFwKGFzeW5jICh1cGRhdGUpID0+IHtcbiAgICAgICAgICBpZiAodXBkYXRlLnR5cGUgPT09IFwianMtdXBkYXRlXCIpIHtcbiAgICAgICAgICAgIHJldHVybiBobXJDbGllbnQucXVldWVVcGRhdGUodXBkYXRlKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgY29uc3QgeyBwYXRoLCB0aW1lc3RhbXAgfSA9IHVwZGF0ZTtcbiAgICAgICAgICBjb25zdCBzZWFyY2hVcmwgPSBjbGVhblVybChwYXRoKTtcbiAgICAgICAgICBjb25zdCBlbCA9IEFycmF5LmZyb20oXG4gICAgICAgICAgICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKFwibGlua1wiKVxuICAgICAgICAgICkuZmluZChcbiAgICAgICAgICAgIChlKSA9PiAhb3V0ZGF0ZWRMaW5rVGFncy5oYXMoZSkgJiYgY2xlYW5VcmwoZS5ocmVmKS5pbmNsdWRlcyhzZWFyY2hVcmwpXG4gICAgICAgICAgKTtcbiAgICAgICAgICBpZiAoIWVsKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgfVxuICAgICAgICAgIGNvbnN0IG5ld1BhdGggPSBgJHtiYXNlfSR7c2VhcmNoVXJsLnNsaWNlKDEpfSR7c2VhcmNoVXJsLmluY2x1ZGVzKFwiP1wiKSA/IFwiJlwiIDogXCI/XCJ9dD0ke3RpbWVzdGFtcH1gO1xuICAgICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgbmV3TGlua1RhZyA9IGVsLmNsb25lTm9kZSgpO1xuICAgICAgICAgICAgbmV3TGlua1RhZy5ocmVmID0gbmV3IFVSTChuZXdQYXRoLCBlbC5ocmVmKS5ocmVmO1xuICAgICAgICAgICAgY29uc3QgcmVtb3ZlT2xkRWwgPSAoKSA9PiB7XG4gICAgICAgICAgICAgIGVsLnJlbW92ZSgpO1xuICAgICAgICAgICAgICBjb25zb2xlLmRlYnVnKGBbdml0ZV0gY3NzIGhvdCB1cGRhdGVkOiAke3NlYXJjaFVybH1gKTtcbiAgICAgICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIG5ld0xpbmtUYWcuYWRkRXZlbnRMaXN0ZW5lcihcImxvYWRcIiwgcmVtb3ZlT2xkRWwpO1xuICAgICAgICAgICAgbmV3TGlua1RhZy5hZGRFdmVudExpc3RlbmVyKFwiZXJyb3JcIiwgcmVtb3ZlT2xkRWwpO1xuICAgICAgICAgICAgb3V0ZGF0ZWRMaW5rVGFncy5hZGQoZWwpO1xuICAgICAgICAgICAgZWwuYWZ0ZXIobmV3TGlua1RhZyk7XG4gICAgICAgICAgfSk7XG4gICAgICAgIH0pXG4gICAgICApO1xuICAgICAgbm90aWZ5TGlzdGVuZXJzKFwidml0ZTphZnRlclVwZGF0ZVwiLCBwYXlsb2FkKTtcbiAgICAgIGJyZWFrO1xuICAgIGNhc2UgXCJjdXN0b21cIjoge1xuICAgICAgbm90aWZ5TGlzdGVuZXJzKHBheWxvYWQuZXZlbnQsIHBheWxvYWQuZGF0YSk7XG4gICAgICBicmVhaztcbiAgICB9XG4gICAgY2FzZSBcImZ1bGwtcmVsb2FkXCI6XG4gICAgICBub3RpZnlMaXN0ZW5lcnMoXCJ2aXRlOmJlZm9yZUZ1bGxSZWxvYWRcIiwgcGF5bG9hZCk7XG4gICAgICBpZiAoaGFzRG9jdW1lbnQpIHtcbiAgICAgICAgaWYgKHBheWxvYWQucGF0aCAmJiBwYXlsb2FkLnBhdGguZW5kc1dpdGgoXCIuaHRtbFwiKSkge1xuICAgICAgICAgIGNvbnN0IHBhZ2VQYXRoID0gZGVjb2RlVVJJKGxvY2F0aW9uLnBhdGhuYW1lKTtcbiAgICAgICAgICBjb25zdCBwYXlsb2FkUGF0aCA9IGJhc2UgKyBwYXlsb2FkLnBhdGguc2xpY2UoMSk7XG4gICAgICAgICAgaWYgKHBhZ2VQYXRoID09PSBwYXlsb2FkUGF0aCB8fCBwYXlsb2FkLnBhdGggPT09IFwiL2luZGV4Lmh0bWxcIiB8fCBwYWdlUGF0aC5lbmRzV2l0aChcIi9cIikgJiYgcGFnZVBhdGggKyBcImluZGV4Lmh0bWxcIiA9PT0gcGF5bG9hZFBhdGgpIHtcbiAgICAgICAgICAgIHBhZ2VSZWxvYWQoKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHBhZ2VSZWxvYWQoKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgYnJlYWs7XG4gICAgY2FzZSBcInBydW5lXCI6XG4gICAgICBub3RpZnlMaXN0ZW5lcnMoXCJ2aXRlOmJlZm9yZVBydW5lXCIsIHBheWxvYWQpO1xuICAgICAgYXdhaXQgaG1yQ2xpZW50LnBydW5lUGF0aHMocGF5bG9hZC5wYXRocyk7XG4gICAgICBicmVhaztcbiAgICBjYXNlIFwiZXJyb3JcIjoge1xuICAgICAgbm90aWZ5TGlzdGVuZXJzKFwidml0ZTplcnJvclwiLCBwYXlsb2FkKTtcbiAgICAgIGlmIChoYXNEb2N1bWVudCkge1xuICAgICAgICBjb25zdCBlcnIgPSBwYXlsb2FkLmVycjtcbiAgICAgICAgaWYgKGVuYWJsZU92ZXJsYXkpIHtcbiAgICAgICAgICBjcmVhdGVFcnJvck92ZXJsYXkoZXJyKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBjb25zb2xlLmVycm9yKFxuICAgICAgICAgICAgYFt2aXRlXSBJbnRlcm5hbCBTZXJ2ZXIgRXJyb3JcbiR7ZXJyLm1lc3NhZ2V9XG4ke2Vyci5zdGFja31gXG4gICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgYnJlYWs7XG4gICAgfVxuICAgIGRlZmF1bHQ6IHtcbiAgICAgIGNvbnN0IGNoZWNrID0gcGF5bG9hZDtcbiAgICAgIHJldHVybiBjaGVjaztcbiAgICB9XG4gIH1cbn1cbmZ1bmN0aW9uIG5vdGlmeUxpc3RlbmVycyhldmVudCwgZGF0YSkge1xuICBobXJDbGllbnQubm90aWZ5TGlzdGVuZXJzKGV2ZW50LCBkYXRhKTtcbn1cbmNvbnN0IGVuYWJsZU92ZXJsYXkgPSB0cnVlO1xuY29uc3QgaGFzRG9jdW1lbnQgPSBcImRvY3VtZW50XCIgaW4gZ2xvYmFsVGhpcztcbmZ1bmN0aW9uIGNyZWF0ZUVycm9yT3ZlcmxheShlcnIpIHtcbiAgY2xlYXJFcnJvck92ZXJsYXkoKTtcbiAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChuZXcgRXJyb3JPdmVybGF5KGVycikpO1xufVxuZnVuY3Rpb24gY2xlYXJFcnJvck92ZXJsYXkoKSB7XG4gIGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwob3ZlcmxheUlkKS5mb3JFYWNoKChuKSA9PiBuLmNsb3NlKCkpO1xufVxuZnVuY3Rpb24gaGFzRXJyb3JPdmVybGF5KCkge1xuICByZXR1cm4gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbChvdmVybGF5SWQpLmxlbmd0aDtcbn1cbmFzeW5jIGZ1bmN0aW9uIHdhaXRGb3JTdWNjZXNzZnVsUGluZyhzb2NrZXRQcm90b2NvbDIsIGhvc3RBbmRQYXRoLCBtcyA9IDFlMykge1xuICBjb25zdCBwaW5nSG9zdFByb3RvY29sID0gc29ja2V0UHJvdG9jb2wyID09PSBcIndzc1wiID8gXCJodHRwc1wiIDogXCJodHRwXCI7XG4gIGNvbnN0IHBpbmcgPSBhc3luYyAoKSA9PiB7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IGZldGNoKGAke3BpbmdIb3N0UHJvdG9jb2x9Oi8vJHtob3N0QW5kUGF0aH1gLCB7XG4gICAgICAgIG1vZGU6IFwibm8tY29yc1wiLFxuICAgICAgICBoZWFkZXJzOiB7XG4gICAgICAgICAgLy8gQ3VzdG9tIGhlYWRlcnMgd29uJ3QgYmUgaW5jbHVkZWQgaW4gYSByZXF1ZXN0IHdpdGggbm8tY29ycyBzbyAoYWIpdXNlIG9uZSBvZiB0aGVcbiAgICAgICAgICAvLyBzYWZlbGlzdGVkIGhlYWRlcnMgdG8gaWRlbnRpZnkgdGhlIHBpbmcgcmVxdWVzdFxuICAgICAgICAgIEFjY2VwdDogXCJ0ZXh0L3gtdml0ZS1waW5nXCJcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9IGNhdGNoIHtcbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9O1xuICBpZiAoYXdhaXQgcGluZygpKSB7XG4gICAgcmV0dXJuO1xuICB9XG4gIGF3YWl0IHdhaXQobXMpO1xuICB3aGlsZSAodHJ1ZSkge1xuICAgIGlmIChkb2N1bWVudC52aXNpYmlsaXR5U3RhdGUgPT09IFwidmlzaWJsZVwiKSB7XG4gICAgICBpZiAoYXdhaXQgcGluZygpKSB7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgICAgYXdhaXQgd2FpdChtcyk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGF3YWl0IHdhaXRGb3JXaW5kb3dTaG93KCk7XG4gICAgfVxuICB9XG59XG5mdW5jdGlvbiB3YWl0KG1zKSB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gc2V0VGltZW91dChyZXNvbHZlLCBtcykpO1xufVxuZnVuY3Rpb24gd2FpdEZvcldpbmRvd1Nob3coKSB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4ge1xuICAgIGNvbnN0IG9uQ2hhbmdlID0gYXN5bmMgKCkgPT4ge1xuICAgICAgaWYgKGRvY3VtZW50LnZpc2liaWxpdHlTdGF0ZSA9PT0gXCJ2aXNpYmxlXCIpIHtcbiAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICBkb2N1bWVudC5yZW1vdmVFdmVudExpc3RlbmVyKFwidmlzaWJpbGl0eWNoYW5nZVwiLCBvbkNoYW5nZSk7XG4gICAgICB9XG4gICAgfTtcbiAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKFwidmlzaWJpbGl0eWNoYW5nZVwiLCBvbkNoYW5nZSk7XG4gIH0pO1xufVxuY29uc3Qgc2hlZXRzTWFwID0gLyogQF9fUFVSRV9fICovIG5ldyBNYXAoKTtcbmlmIChcImRvY3VtZW50XCIgaW4gZ2xvYmFsVGhpcykge1xuICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKFwic3R5bGVbZGF0YS12aXRlLWRldi1pZF1cIikuZm9yRWFjaCgoZWwpID0+IHtcbiAgICBzaGVldHNNYXAuc2V0KGVsLmdldEF0dHJpYnV0ZShcImRhdGEtdml0ZS1kZXYtaWRcIiksIGVsKTtcbiAgfSk7XG59XG5jb25zdCBjc3BOb25jZSA9IFwiZG9jdW1lbnRcIiBpbiBnbG9iYWxUaGlzID8gZG9jdW1lbnQucXVlcnlTZWxlY3RvcihcIm1ldGFbcHJvcGVydHk9Y3NwLW5vbmNlXVwiKT8ubm9uY2UgOiB2b2lkIDA7XG5sZXQgbGFzdEluc2VydGVkU3R5bGU7XG5mdW5jdGlvbiB1cGRhdGVTdHlsZShpZCwgY29udGVudCkge1xuICBsZXQgc3R5bGUgPSBzaGVldHNNYXAuZ2V0KGlkKTtcbiAgaWYgKCFzdHlsZSkge1xuICAgIHN0eWxlID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcInN0eWxlXCIpO1xuICAgIHN0eWxlLnNldEF0dHJpYnV0ZShcInR5cGVcIiwgXCJ0ZXh0L2Nzc1wiKTtcbiAgICBzdHlsZS5zZXRBdHRyaWJ1dGUoXCJkYXRhLXZpdGUtZGV2LWlkXCIsIGlkKTtcbiAgICBzdHlsZS50ZXh0Q29udGVudCA9IGNvbnRlbnQ7XG4gICAgaWYgKGNzcE5vbmNlKSB7XG4gICAgICBzdHlsZS5zZXRBdHRyaWJ1dGUoXCJub25jZVwiLCBjc3BOb25jZSk7XG4gICAgfVxuICAgIGlmICghbGFzdEluc2VydGVkU3R5bGUpIHtcbiAgICAgIGRvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoc3R5bGUpO1xuICAgICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgIGxhc3RJbnNlcnRlZFN0eWxlID0gdm9pZCAwO1xuICAgICAgfSwgMCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGxhc3RJbnNlcnRlZFN0eWxlLmluc2VydEFkamFjZW50RWxlbWVudChcImFmdGVyZW5kXCIsIHN0eWxlKTtcbiAgICB9XG4gICAgbGFzdEluc2VydGVkU3R5bGUgPSBzdHlsZTtcbiAgfSBlbHNlIHtcbiAgICBzdHlsZS50ZXh0Q29udGVudCA9IGNvbnRlbnQ7XG4gIH1cbiAgc2hlZXRzTWFwLnNldChpZCwgc3R5bGUpO1xufVxuZnVuY3Rpb24gcmVtb3ZlU3R5bGUoaWQpIHtcbiAgY29uc3Qgc3R5bGUgPSBzaGVldHNNYXAuZ2V0KGlkKTtcbiAgaWYgKHN0eWxlKSB7XG4gICAgZG9jdW1lbnQuaGVhZC5yZW1vdmVDaGlsZChzdHlsZSk7XG4gICAgc2hlZXRzTWFwLmRlbGV0ZShpZCk7XG4gIH1cbn1cbmZ1bmN0aW9uIGNyZWF0ZUhvdENvbnRleHQob3duZXJQYXRoKSB7XG4gIHJldHVybiBuZXcgSE1SQ29udGV4dChobXJDbGllbnQsIG93bmVyUGF0aCk7XG59XG5mdW5jdGlvbiBpbmplY3RRdWVyeSh1cmwsIHF1ZXJ5VG9JbmplY3QpIHtcbiAgaWYgKHVybFswXSAhPT0gXCIuXCIgJiYgdXJsWzBdICE9PSBcIi9cIikge1xuICAgIHJldHVybiB1cmw7XG4gIH1cbiAgY29uc3QgcGF0aG5hbWUgPSB1cmwucmVwbGFjZSgvWz8jXS4qJC8sIFwiXCIpO1xuICBjb25zdCB7IHNlYXJjaCwgaGFzaCB9ID0gbmV3IFVSTCh1cmwsIFwiaHR0cDovL3ZpdGUuZGV2XCIpO1xuICByZXR1cm4gYCR7cGF0aG5hbWV9PyR7cXVlcnlUb0luamVjdH0ke3NlYXJjaCA/IGAmYCArIHNlYXJjaC5zbGljZSgxKSA6IFwiXCJ9JHtoYXNoIHx8IFwiXCJ9YDtcbn1cblxuZXhwb3J0IHsgRXJyb3JPdmVybGF5LCBjcmVhdGVIb3RDb250ZXh0LCBpbmplY3RRdWVyeSwgcmVtb3ZlU3R5bGUsIHVwZGF0ZVN0eWxlIH07XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsTUFBTSxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0FBQ2hEO0FBQ0EsS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDO0FBQ2xCLENBQUMsQ0FBQyxXQUFXLENBQUMsU0FBUyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztBQUNyQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztBQUMvQixDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztBQUMvQixDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzVDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDM0MsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsYUFBYSxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUN2RCxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7QUFDZCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUN6QixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxpQkFBaUIsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUM7QUFDdEUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDO0FBQ3pCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDO0FBQ3ZELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsa0JBQWtCLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQ2xFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0FBQ3hCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLGtCQUFrQixDQUFDLEdBQUcsQ0FBQztBQUMzQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUM7QUFDbEIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUMxRCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNaLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ1QsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNQLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7QUFDbEQsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsaUJBQWlCLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQztBQUNsRSxDQUFDLENBQUMsQ0FBQztBQUNILENBQUMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2QsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUN0RCxDQUFDLENBQUMsQ0FBQztBQUNILENBQUMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztBQUMxQixDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztBQUM5QyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBQ2hFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDMUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBQzFELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNyQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQ3RDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQ1osQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3JELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDO0FBQ0gsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUMsRUFBRTtBQUN4RSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxXQUFXO0FBQzVDLENBQUMsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztBQUM5QixDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7QUFDbEUsQ0FBQyxDQUFDLENBQUM7QUFDSCxDQUFDLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7QUFDZixDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDdEQsQ0FBQyxDQUFDLENBQUM7QUFDSCxDQUFDLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7QUFDYixDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDcEQsQ0FBQyxDQUFDLENBQUM7QUFDSCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUM7QUFDN0MsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxRQUFRO0FBQ2xFLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDYixDQUFDLENBQUMsQ0FBQztBQUNILENBQUMsQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQztBQUN2QixDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsZUFBZSxDQUFDLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUN2RCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDO0FBQzNCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU87QUFDYixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ1AsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDcEUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUM7QUFDaEMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUMzRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNOLENBQUMsQ0FBQyxDQUFDO0FBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ2pCLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQy9CLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDNUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztBQUN4QixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQy9CLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ04sQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDO0FBQ2hELENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQztBQUNoQyxDQUFDLENBQUMsQ0FBQztBQUNILENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztBQUNsQixDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNwQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQ3RDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2hDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUM7QUFDZixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ1AsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBQ3RELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FB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QUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUM3QixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDO0FBQ3RCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUNoSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNOLENBQUMsQ0FBQyxDQUFDO0FBQ0gsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLE1BQU07QUFDakUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDO0FBQy9ELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsR0FBRyxD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QyxFQUFFLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDO0FBQ3RELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7QUFDWCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztBQUMxRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ1YsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNQLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7QUFDN0UsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzdELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ04sQ0FBQyxDQUFDLENBQUM7QUFDSCxDQUFDO0FBQ0Q7QUFDQSxLQUFLLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDdkMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxD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TUFBTSxDQUFDLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDO0FBQ2pDLENBQUMsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDekUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLE1BQU0sQ0FBQztBQUNuQixDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUNuQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsR0FBRyxDQUFDO0FBQ2pCLENBQUMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsSUFBSSxDQUFDO0FBQ25CLENBQUM7QUFDRDtBQUNBLEdBQUcsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQ2hDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDO0FBQ2xCLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2hCLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsR0FBRyxDQUFDO0FBQ3JCLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDO0FBQ3JCLENBQUMsQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUMsSUFBSSxDQUFDO0FBQ3hCLENBQUM7QUFDRDtBQUNBLEdBQUcsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQ3hCLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUM7QUFDaEIsQ0FBQztBQUNEO0FBQ0EsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQzlCLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxLQUFLLENBQUM7QUFDakIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLEdBQUcsQ0FBQztBQUNkLENBQUM7QUFDRDtBQUNBLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDcEMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDO0FBQ25CLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsR0FBRyxDQUFDO0FBQ3JCLENBQUM7QUFDRDtBQUNBLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUNYLENBQUMsQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUMsSUFBSSxDQUFDO0FBQ3hCLENBQUM7QUFDRDtBQUNBLENBQUMsT0FBTyxDQUFDLENBQUM7QUFDVixDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDbkIsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxHQUFHLENBQUM7QUFDbkIsQ0FBQyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDO0FBQ3hCLENBQUM7QUFDRDtBQUNBLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQ2YsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDcEIsQ0FBQztBQUNEO0FBQ0EsQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUNULENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBQ3ZCLENBQUM7QUFDRDtBQUNBLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDUCxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUNyQixDQUFDLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNuQixDQUFDLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUM7QUFDeEIsQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDO0FBQ3hCLENBQUM7QUFDRDtBQUNBLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDUixDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUN2QixDQUFDO0FBQ0Q7QUFDQSxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQ1IsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUM7QUFDbEIsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDcEIsQ0FBQztBQUNEO0FBQ0EsQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUNOLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDO0FBQ2xCLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQztBQUNkLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLEdBQUcsQ0FBQztBQUM5QixDQUFDLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQztBQUNwQixDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDbkIsQ0FBQztBQUNEO0FBQ0EsSUFBSSxDQUFDLENBQUM7QUFDTixDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksQ0FBQztBQUNsQixDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUM7QUFDaEMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUM7QUFDdkIsQ0FBQztBQUNEO0FBQ0EsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDWixDQUFDLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDLFNBQVMsQ0FBQztBQUM3QixDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsT0FBTyxDQUFDO0FBQ2xCLENBQUM7QUFDRDtBQUNBLEdBQUcsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ25CLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxDQUFDLFNBQVMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7QUFDbEcsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDO0FBQ3JCLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsR0FBRyxDQUFDO0FBQ25CLENBQUMsQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBQ3BDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDNUIsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztBQUMxQixDQUFDLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUM7QUFDekIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQztBQUM5QyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLEtBQUssQ0FBQztBQUN0QixDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQztBQUNoQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLE9BQU8sQ0FBQztBQUN4QixDQUFDO0FBQ0QsQ0FBQztBQUNELENBQUMsQ0FBQztBQUNGLEtBQUssQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUMvQixDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUNSLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDSixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDVixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDeEMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDTixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0FBQ1osQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUM1QyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNyRCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNoRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNOLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDOUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNoRCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2hELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ04sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUNaLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDcEMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztBQUM5QixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBQzFCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7QUFDMUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztBQUNkLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7QUFDdEQsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0FBQ3RFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ2IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztBQUMxRCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztBQUNiLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxhQUFhLENBQUMsQ0FBQztBQUM3RCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNULENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDSixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLGFBQWEsQ0FBQztBQUMvQixDQUFDLENBQUM7QUFDRixLQUFLLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDaEQsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7QUFDL0QsS0FBSyxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQzdCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQztBQUNqQixLQUFLLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUN4QyxDQUFDLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztBQUNsQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7QUFDWixDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDcEQsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDNUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDOUIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQzlELENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDO0FBQ2xGLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFDckIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUN0RCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDL0MsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzFFLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7QUFDbEIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDN0UsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ3hCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDL0IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDO0FBQ25CLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDNUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUMxQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDekUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUM7QUFDMUIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNQLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO0FBQ25CLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDUCxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUM5QixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDdEQsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO0FBQ3JCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDUCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNOLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLGdCQUFnQixDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7QUFDMUQsQ0FBQyxDQUFDLENBQUM7QUFDSCxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztBQUMzQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQ2pELENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0FBQ3JCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztBQUM1QixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUNaLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUN2QixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDO0FBQ2hCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUMzQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3pDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQztBQUN6QyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7QUFDNUIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQ25ELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLFdBQVcsQ0FBQyxRQUFRLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7QUFDeEQsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNuRCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztBQUNsQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQ3ZDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNoQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUM7QUFDbEIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUM7QUFDdEIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsa0JBQWtCLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzdFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHO0FBQy9CLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2YsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2QsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDWixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDL0IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQztBQUNoRCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNULENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDUCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQztBQUNILENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDWCxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDdkMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsbUJBQW1CLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztBQUM3RCxDQUFDLENBQUMsQ0FBQztBQUNILENBQUM7QUFDRCxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7QUFDdkMsS0FBSyxDQUFDLENBQUMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUM7QUFDdEMsRUFBRSxDQUFDLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDdkQsQ0FBQyxDQUFDLGNBQWMsQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsWUFBWSxDQUFDLENB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QUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDO0FBQ3ZCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQ3BLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ1osQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNWLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDdEIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ1IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDTixDQUFDLENBQUMsQ0FBQztBQUNILENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLGNBQWMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQztBQUNoRSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztBQUNqQixDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsTUFBTSxDQUFDLEVB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LENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDdkksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDTixDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLHNCQUFzQixDQUFDLENBQUMsQ0FBQztBQUNqQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDakMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUM7QUFDckIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBQ2hSLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDVixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQztBQUNyQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNULENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxhQUFhLENBQUM7QUFDL0IsQ0FBQyxDQUFDLENBQUM7QUFDSCxDQUFDLENBQUM7QUFDRixLQUFLLENBQUMsUUFBUSxDQUFDLGFBQWEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0FBQ3ZDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztBQUN6QixDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQ3JCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUN6QyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7QUFDbEMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDekIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztBQUNoRCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUN6QyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNULENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQ2hCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQztBQUNaLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUM7QUFDbEIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsZUFBZSxDQUFDLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUM7QUFDcEQsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQztBQUN4QixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2pELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFDNUIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUM7QUFDakIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUNoQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUM7QUFDOUIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDO0FBQ2hDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNYLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUM7QUFDaEMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDVCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ1AsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUM7QUFDeEIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDL0MsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzVDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBQ2pELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNYLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUM7QUFDN0MsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDM0MsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDO0FBQ2hDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQzdDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztBQUNqQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQztBQUNuRixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNaLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ3BCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQztBQUNuQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDWCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0FBQzdHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzNDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0FBQzlDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksQ0FBQztBQUM3RCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3ZDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztBQUMxQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3BFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0FBQ3hCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNkLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsV0FBVyxDQUFDLENBQUM7QUFDN0QsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLGdCQUFnQixDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUM5RCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDckMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQztBQUNqQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNWLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNSLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLGVBQWUsQ0FBQyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0FBQ25ELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQztBQUNaLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3BCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLGVBQWUsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQ25ELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQztBQUNaLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBQ3ZCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLGVBQWUsQ0FBQyxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUM7QUFDeEQsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQztBQUN4QixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDN0QsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQ3hELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUMzRCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQztBQUNoSixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDO0FBQ3pCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNYLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDO0FBQ2pCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDaEIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDO0FBQ3ZCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ1QsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNQLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQztBQUNaLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDakIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsZUFBZSxDQUFDLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUM7QUFDbkQsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQ2hELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQztBQUNaLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ25CLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLGVBQWUsQ0FBQyxDQUFDLElBQUks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T0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRztBQUM3RixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLE9BQU87QUFDNUQsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQztBQUNwQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNULENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ1QsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQztBQUNsQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUNiLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQztBQUNqQixDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ0osQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNyQixDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQztBQUNYLENBQUMsQ0FBQyxDQUFDO0FBQ0gsQ0FBQyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDakIsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7QUFDaEIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDakQsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3pCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUM7QUFDZCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ1AsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztBQUNyQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUNaLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDLENBQUM7QUFDaEMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUM7QUFDSCxDQUFDO0FBQ0QsUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ25CLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQzNELENBQUM7QUFDRCxRQUFRLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDOUIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNuQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2xDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNuRCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQztBQUNsQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLG1CQUFtQixDQUFDLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQ25FLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDUCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNOLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLGdCQUFnQixDQUFDLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQzVELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUM7QUFDRCxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztBQUM1QyxFQUFFLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDO0FBQy9CLENBQUMsQ0FBQyxRQUFRLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FB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QUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUN6QyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUM7QUFDZixDQUFDLENBQUMsQ0FBQztBQUNILENBQUMsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzlDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBQzNELENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUMzRixDQUFDO0FBQ0Q7QUFDQSxNQUFNLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQzsifQ== \ No newline at end of file diff --git a/ruoyi-ui/public/preview/preview_files/erweima.png b/ruoyi-ui/public/preview/preview_files/erweima.png new file mode 100644 index 0000000000000000000000000000000000000000..65f3b6fb20b3ee67b2a99efd903986c131e4cd16 Binary files /dev/null and b/ruoyi-ui/public/preview/preview_files/erweima.png differ diff --git a/ruoyi-ui/public/preview/preview_files/favicon.ico b/ruoyi-ui/public/preview/preview_files/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..bb4bf76733b48264f1abc4e4f2b765f5d992e8ce Binary files /dev/null and b/ruoyi-ui/public/preview/preview_files/favicon.ico differ diff --git a/ruoyi-ui/public/preview/preview_files/first1.png b/ruoyi-ui/public/preview/preview_files/first1.png new file mode 100644 index 0000000000000000000000000000000000000000..f74fbed434f4ce88655670a7b853e8b92cfac684 Binary files /dev/null and b/ruoyi-ui/public/preview/preview_files/first1.png differ diff --git a/ruoyi-ui/public/preview/preview_files/first2.png b/ruoyi-ui/public/preview/preview_files/first2.png new file mode 100644 index 0000000000000000000000000000000000000000..ed4844b18452882d876f6e825add182147aac867 Binary files /dev/null and b/ruoyi-ui/public/preview/preview_files/first2.png differ diff --git a/ruoyi-ui/public/preview/preview_files/first3.png b/ruoyi-ui/public/preview/preview_files/first3.png new file mode 100644 index 0000000000000000000000000000000000000000..74322323dc680339fd8a9a16c738e96be2b97959 Binary files /dev/null and b/ruoyi-ui/public/preview/preview_files/first3.png differ diff --git a/ruoyi-ui/public/preview/preview_files/first4.png b/ruoyi-ui/public/preview/preview_files/first4.png new file mode 100644 index 0000000000000000000000000000000000000000..008fc2fda93c8356bf75f3e820c31c1f0ef43f2a Binary files /dev/null and b/ruoyi-ui/public/preview/preview_files/first4.png differ diff --git a/ruoyi-ui/public/preview/preview_files/first5.png b/ruoyi-ui/public/preview/preview_files/first5.png new file mode 100644 index 0000000000000000000000000000000000000000..b2d5f9847e894caa59ff9fb9e38cde7613fb5fa0 Binary files /dev/null and b/ruoyi-ui/public/preview/preview_files/first5.png differ diff --git a/ruoyi-ui/public/preview/preview_files/header.png b/ruoyi-ui/public/preview/preview_files/header.png new file mode 100644 index 0000000000000000000000000000000000000000..bfd2882f7b8877c64d16fbb53c49fe9f0dd61a35 Binary files /dev/null and b/ruoyi-ui/public/preview/preview_files/header.png differ diff --git "a/ruoyi-ui/public/preview/preview_files/main.js.\344\270\213\350\275\275" "b/ruoyi-ui/public/preview/preview_files/main.js.\344\270\213\350\275\275" new file mode 100644 index 0000000000000000000000000000000000000000..005c7109e0155beb67b6d4d2b58d40bca47157c4 --- /dev/null +++ "b/ruoyi-ui/public/preview/preview_files/main.js.\344\270\213\350\275\275" @@ -0,0 +1,61 @@ +// import './assets/main.css' +import "/src/assets/global.css" + +import { createApp } from "/node_modules/.vite/deps/vue.js?v=d0071ae4" + +import router from "/src/route/index.js?t=1762152847161" +// 引入elemnetplus +import ElementPlus from "/node_modules/.vite/deps/element-plus.js?v=d0071ae4" +import "/node_modules/element-plus/dist/index.css" +import App from "/src/App.vue?t=1762152847161" +// import globalMethods from '@/plugins/globalMethods' +import { ArtByColumnId,formatAllDate,getLatestNews,truncateText, + formatDate,truncateRichText,ArtDetails,truRichText, + getListKind,truRichTextFixed,highlightKeyword + + } from "/src/plugins/globalMethods.js" + + + +const app = createApp(App) +// 挂载 +app.use(ElementPlus) + + +// 注册全局方法 + +// 1.格式化日期格式 +app.config.globalProperties.$formatAllDate = formatAllDate + +// 2.根据conlumnId获取文章列表 +app.config.globalProperties.$ArtByColumnId = ArtByColumnId +// 3.获取最新文章并选取特定信息数量 +app.config.globalProperties.$getLatestNews = getLatestNews +// 4.格式化日期,例:2025-03-01 提取出来就是2025-03和01 +app.config.globalProperties.$formatDate = formatDate +// 5. 截取文本,不想显示全部,只显示前20个字等等 +app.config.globalProperties.$truncateText = truncateText + +// 5. 截取富文本 +app.config.globalProperties.$truncateRichText = truncateRichText +// 6.获取文章详细信息 +app.config.globalProperties.$ArtDetails = ArtDetails +// 7.截取富文本(新) +app.config.globalProperties.$truRichText = truRichText +// 8.截取富文本(考虑字符宽度,专门用于firstPage.vue) +app.config.globalProperties.$truRichTextFixed = truRichTextFixed +// 9. +app.config.globalProperties.$getListKind = getListKind +// 10.高亮关键字 +app.config.globalProperties.$highlightKeyword = highlightKeyword + + + + + + + + + +app.use(router).mount('#app') +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4uanMiXSwic291cmNlc0NvbnRlbnQiOlsiLy8gaW1wb3J0ICcuL2Fzc2V0cy9tYWluLmNzcydcclxuaW1wb3J0IFwiL3NyYy9hc3NldHMvZ2xvYmFsLmNzc1wiXHJcblxyXG5pbXBvcnQgeyBjcmVhdGVBcHAgfSBmcm9tIFwiL25vZGVfbW9kdWxlcy8udml0ZS9kZXBzL3Z1ZS5qcz92PWQwMDcxYWU0XCJcclxuXHJcbmltcG9ydCByb3V0ZXIgZnJvbSBcIi9zcmMvcm91dGUvaW5kZXguanM/dD0xNzYyMTUyODQ3MTYxXCJcclxuLy8g5byV5YWlZWxlbW5ldHBsdXNcclxuaW1wb3J0IEVsZW1lbnRQbHVzIGZyb20gXCIvbm9kZV9tb2R1bGVzLy52aXRlL2RlcHMvZWxlbWVudC1wbHVzLmpzP3Y9ZDAwNzFhZTRcIlxyXG5pbXBvcnQgXCIvbm9kZV9tb2R1bGVzL2VsZW1lbnQtcGx1cy9kaXN0L2luZGV4LmNzc1wiXHJcbmltcG9ydCBBcHAgZnJvbSBcIi9zcmMvQXBwLnZ1ZT90PTE3NjIxNTI4NDcxNjFcIlxyXG4vLyBpbXBvcnQgZ2xvYmFsTWV0aG9kcyBmcm9tICdAL3BsdWdpbnMvZ2xvYmFsTWV0aG9kcydcclxuaW1wb3J0IHsgQXJ0QnlDb2x1bW5JZCxmb3JtYXRBbGxEYXRlLGdldExhdGVzdE5ld3MsdHJ1bmNhdGVUZXh0LFxyXG4gICAgZm9ybWF0RGF0ZSx0cnVuY2F0ZVJpY2hUZXh0LEFydERldGFpbHMsdHJ1UmljaFRleHQsXHJcbiAgICBnZXRMaXN0S2luZCx0cnVSaWNoVGV4dEZpeGVkLGhpZ2hsaWdodEtleXdvcmRcclxuXHJcbiB9IGZyb20gXCIvc3JjL3BsdWdpbnMvZ2xvYmFsTWV0aG9kcy5qc1wiXHJcblxyXG5cclxuXHJcbmNvbnN0IGFwcCA9IGNyZWF0ZUFwcChBcHApXHJcbi8vIOaMgui9vVxyXG5hcHAudXNlKEVsZW1lbnRQbHVzKVxyXG5cclxuXHJcbi8vIOazqOWGjOWFqOWxgOaWueazlVxyXG5cclxuLy8gMS7moLzlvI/ljJbml6XmnJ/moLzlvI9cclxuYXBwLmNvbmZpZy5nbG9iYWxQcm9wZXJ0aWVzLiRmb3JtYXRBbGxEYXRlID0gZm9ybWF0QWxsRGF0ZVxyXG5cclxuLy8gMi7moLnmja5jb25sdW1uSWTojrflj5bmlofnq6DliJfooahcclxuYXBwLmNvbmZpZy5nbG9iYWxQcm9wZXJ0aWVzLiRBcnRCeUNvbHVtbklkID0gQXJ0QnlDb2x1bW5JZFxyXG4vLyAzLuiOt+WPluacgOaWsOaWh+eroOW5tumAieWPlueJueWumuS/oeaBr+aVsOmHj1xyXG5hcHAuY29uZmlnLmdsb2JhbFByb3BlcnRpZXMuJGdldExhdGVzdE5ld3MgPSBnZXRMYXRlc3ROZXdzXHJcbi8vIDQu5qC85byP5YyW5pel5pyfLOS+i++8mjIwMjUtMDMtMDEg5o+Q5Y+W5Ye65p2l5bCx5pivMjAyNS0wM+WSjDAxXHJcbmFwcC5jb25maWcuZ2xvYmFsUHJvcGVydGllcy4kZm9ybWF0RGF0ZSA9IGZvcm1hdERhdGVcclxuLy8gNS4g5oiq5Y+W5paH5pys77yM5LiN5oOz5pi+56S65YWo6YOo77yM5Y+q5pi+56S65YmNMjDkuKrlrZfnrYnnrYlcclxuYXBwLmNvbmZpZy5nbG9iYWxQcm9wZXJ0aWVzLiR0cnVuY2F0ZVRleHQgPSB0cnVuY2F0ZVRleHRcclxuXHJcbi8vIDUuIOaIquWPluWvjOaWh+acrFxyXG5hcHAuY29uZmlnLmdsb2JhbFByb3BlcnRpZXMuJHRydW5jYXRlUmljaFRleHQgPSB0cnVuY2F0ZVJpY2hUZXh0XHJcbi8vIDYu6I635Y+W5paH56ug6K+m57uG5L+h5oGvXHJcbmFwcC5jb25maWcuZ2xvYmFsUHJvcGVydGllcy4kQXJ0RGV0YWlscyA9IEFydERldGFpbHNcclxuLy8gNy7miKrlj5blr4zmlofmnKwo5pawKVxyXG5hcHAuY29uZmlnLmdsb2JhbFByb3BlcnRpZXMuJHRydVJpY2hUZXh0ID0gdHJ1UmljaFRleHRcclxuLy8gOC7miKrlj5blr4zmlofmnKwo6ICD6JmR5a2X56ym5a695bqm77yM5LiT6Zeo55So5LqOZmlyc3RQYWdlLnZ1ZSlcclxuYXBwLmNvbmZpZy5nbG9iYWxQcm9wZXJ0aWVzLiR0cnVSaWNoVGV4dEZpeGVkID0gdHJ1UmljaFRleHRGaXhlZFxyXG4vLyA5LlxyXG5hcHAuY29uZmlnLmdsb2JhbFByb3BlcnRpZXMuJGdldExpc3RLaW5kID0gZ2V0TGlzdEtpbmRcclxuLy8gMTAu6auY5Lqu5YWz6ZSu5a2XXHJcbmFwcC5jb25maWcuZ2xvYmFsUHJvcGVydGllcy4kaGlnaGxpZ2h0S2V5d29yZCA9IGhpZ2hsaWdodEtleXdvcmRcclxuXHJcblxyXG5cclxuXHJcblxyXG5cclxuXHJcblxyXG5cclxuYXBwLnVzZShyb3V0ZXIpLm1vdW50KCcjYXBwJykiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDOUIsTUFBTSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUNoQyxDQUFDO0FBQ0QsTUFBTSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsWUFBWSxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQztBQUN2RSxDQUFDO0FBQ0QsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLENBQUM7QUFDekQsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFdBQVcsQ0FBQztBQUNqQixNQUFNLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsWUFBWSxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUM7QUFDOUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0FBQ25ELE1BQU0sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLENBQUM7QUFDL0MsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDO0FBQ3ZELE1BQU0sQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLGFBQWEsQ0FBQyxhQUFhLENBQUMsWUFBWSxDQUFDLENBQUM7QUFDakUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsZ0JBQWdCLENBQUMsVUFBVSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0FBQ3hELENBQUMsQ0FBQyxDQUFDLENBQUMsV0FBVyxDQUFDLGdCQUFnQixDQUFDLGdCQUFnQixDQUFDO0FBQ2xELENBQUM7QUFDRCxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsYUFBYSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBQ3hDLENBQUM7QUFDRCxDQUFDO0FBQ0QsQ0FBQztBQUNELEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUMzQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNOLEdBQUcsQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLENBQUM7QUFDckIsQ0FBQztBQUNELENBQUM7QUFDRCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ1YsQ0FBQztBQUNELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDYixHQUFHLENBQUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxhQUFhLENBQUM7QUFDM0QsQ0FBQztBQUNELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3ZCLEdBQUcsQ0FBQyxNQUFNLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLGFBQWEsQ0FBQztBQUMzRCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNyQixHQUFHLENBQUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxhQUFhLENBQUM7QUFDM0QsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQztBQUN6QyxHQUFHLENBQUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUM7QUFDckQsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUM3QixHQUFHLENBQUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUM7QUFDekQsQ0FBQztBQUNELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ1osR0FBRyxDQUFDLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxnQkFBZ0IsQ0FBQztBQUNqRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDZCxHQUFHLENBQUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUM7QUFDckQsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2QsR0FBRyxDQUFDLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsV0FBVyxDQUFDO0FBQ3ZELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0FBQ3JDLEdBQUcsQ0FBQyxNQUFNLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLENBQUM7QUFDakUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDTixHQUFHLENBQUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUM7QUFDdkQsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ1osR0FBRyxDQUFDLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxnQkFBZ0IsQ0FBQztBQUNqRSxDQUFDO0FBQ0QsQ0FBQztBQUNELENBQUM7QUFDRCxDQUFDO0FBQ0QsQ0FBQztBQUNELENBQUM7QUFDRCxDQUFDO0FBQ0QsQ0FBQztBQUNELENBQUM7QUFDRCxHQUFHLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDIn0= \ No newline at end of file diff --git a/ruoyi-ui/src/api/article/article.js b/ruoyi-ui/src/api/article/article.js index 5c1fc95750a8dde8a81787509612bd1db9c1722d..a8bde146d8074c3bbb41cdd70f4186caa05cc268 100644 --- a/ruoyi-ui/src/api/article/article.js +++ b/ruoyi-ui/src/api/article/article.js @@ -30,15 +30,17 @@ export function uploadAppval(id, formData) { }); } -// 附件上传 -export function uploadAttachment(id, formData) { +// pdf上传 +export function uploadAttachment(id, formData, signal) { return request({ url: '/article/article/attachment/' + id, method: 'post', data: formData, headers: { 'Content-Type': 'multipart/form-data' - } + }, + timeout: 300000, // 延长超时时间到5分钟 + signal: signal // 添加取消信号 }); } diff --git a/ruoyi-ui/src/api/chartColumn/chartColumn.js b/ruoyi-ui/src/api/chartColumn/chartColumn.js new file mode 100644 index 0000000000000000000000000000000000000000..dad6f29ebbd0981290fa6132e7ca7d000095fb78 --- /dev/null +++ b/ruoyi-ui/src/api/chartColumn/chartColumn.js @@ -0,0 +1,53 @@ +import request from '@/utils/request' + +// 查询首页图表栏目动态显示列表 +export function listChartColumn(query) { + return request({ + url: '/chartColumn/chartColumn/list', + method: 'get', + params: query + }) +} + +// 查询首页图表栏目动态显示详细 +export function getChartColumn(id) { + return request({ + url: '/chartColumn/chartColumn/' + id, + method: 'get' + }) +} + +// 新增首页图表栏目动态显示 +export function addChartColumn(data) { + return request({ + url: '/chartColumn/chartColumn', + method: 'post', + data: data + }) +} + +// 修改首页图表栏目动态显示 +export function updateChartColumn(data) { + return request({ + url: '/chartColumn/chartColumn', + method: 'put', + data: data + }) +} + +// 删除首页图表栏目动态显示 +export function delChartColumn(id) { + return request({ + url: '/chartColumn/chartColumn/' + id, + method: 'delete' + }) +} + +// 获取显示状态的图表栏目列表 +export function listDisplayChartColumn() { + return request({ + url: '/chartColumn/chartColumn/list', + method: 'get', + params: { status: 0 } + }) +} diff --git a/ruoyi-ui/src/api/dashboard/index.js b/ruoyi-ui/src/api/dashboard/index.js index 08bba25a9300108ed2ce38ae37e9ee0b0b0aa742..b006812a6b9bf95e28b18f4e03f4010ab10686fe 100644 --- a/ruoyi-ui/src/api/dashboard/index.js +++ b/ruoyi-ui/src/api/dashboard/index.js @@ -10,11 +10,11 @@ export const getGraph1 = (startTime,endTime) => { } // 获取按部门统计的图表数据 -export const getGraph1ByDept = (startTime,endTime) => { +export const getGraph1ByDept = (startTime,endTime,columnIds) => { return request({ url: '/article/article/deptArticleCount', method: 'get', - params:{startTime,endTime} + params:{startTime,endTime,columnIds} }) } diff --git a/ruoyi-ui/src/api/intergrity-recommend/index.js b/ruoyi-ui/src/api/intergrity-recommend/index.js index 29cc63e6123800c1d908ff07bc06824c335c0868..8985759404c7d8b87e6d289ad6b42af8f6abe310 100644 --- a/ruoyi-ui/src/api/intergrity-recommend/index.js +++ b/ruoyi-ui/src/api/intergrity-recommend/index.js @@ -3,7 +3,7 @@ import request from '@/utils/request' // 分页查询文章列表 export function listArticle(params) { return request({ - url: '/textRead/textRead/list', + url: '/textRead/textRead/endList', method: 'get', params: params }) diff --git a/ruoyi-ui/src/api/partyclass/partyclass.js b/ruoyi-ui/src/api/partyclass/partyclass.js index 9ade3ba247e29f6056e101ca78eecae5181d41df..5c15aa2b6b73e080cc5a15f141da0e6f82fce667 100644 --- a/ruoyi-ui/src/api/partyclass/partyclass.js +++ b/ruoyi-ui/src/api/partyclass/partyclass.js @@ -10,6 +10,16 @@ export function listPartyclass(query) { }) } +// 查询partyclass列表 +export function listPartyList(query) { + return request({ + url: '/partyclass/partyclass/parList', + method: 'post', + params: query, + data:query + }) +} + // 查询partyclass详细 export function getPartyclass(id) { return request({ @@ -100,4 +110,4 @@ export function publishPartyVideo(id) { url: '/partyclass/partyclass/publish/' + id, method: 'put' }) -} +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/uncheck/uncheck.js b/ruoyi-ui/src/api/uncheck/uncheck.js index 2d723ebda3f90f4d66faba1c3a177463fcda7c43..fd2649c7d265e0a14d2944e93f44d6466cdacf5f 100644 --- a/ruoyi-ui/src/api/uncheck/uncheck.js +++ b/ruoyi-ui/src/api/uncheck/uncheck.js @@ -22,15 +22,17 @@ export function uploadUncheckAppval(id, formData) { }); } -// 附件上传 -export function uploadUncheckAttachment(id, formData) { +// pdf上传 +export function uploadUncheckAttachment(id, formData, signal) { return request({ url: '/uncheck/uncheck/attachment/' + id, method: 'post', data: formData, headers: { 'Content-Type': 'multipart/form-data' - } + }, + timeout: 300000, // 延长超时时间到5分钟 + signal: signal // 添加取消信号 }); } @@ -123,12 +125,21 @@ export function logicDeleteUncheck(articleIds) { // 修改uncheck export function updateUncheck(data) { return request({ - url: '/uncheck/uncheck', + url: '/uncheck/uncheck/', + method: 'put', + data: data + }) +} +//驳回uncheck +export function updateUncheck1(data) { + return request({ + url: '/uncheck/uncheck/updateState', method: 'put', data: data }) } + // 删除uncheck export function delUncheck(articleId) { return request({ @@ -136,3 +147,13 @@ export function delUncheck(articleId) { method: 'delete' }) } + +// 导出uncheck +export function exportUncheck(query) { + return request({ + url: '/uncheck/uncheck/export', + method: 'post', + data: query, + responseType: 'blob' + }) +} diff --git a/ruoyi-ui/src/assets/images/login-bg.png b/ruoyi-ui/src/assets/images/login-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..23595ebf85362e99d448567f7b638380d16be259 Binary files /dev/null and b/ruoyi-ui/src/assets/images/login-bg.png differ diff --git a/ruoyi-ui/src/assets/images/logo.png b/ruoyi-ui/src/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6b46a452c4150e9f395fec2b8177cd8df2394a0e Binary files /dev/null and b/ruoyi-ui/src/assets/images/logo.png differ diff --git a/ruoyi-ui/src/components/Editor/EnhancedEditor.vue b/ruoyi-ui/src/components/Editor/EnhancedEditor.vue index d211ff49c103d466f8aeaed85dce15647a5b0ca3..43bdae829111a2cd0c74f910412d76002d382272 100644 --- a/ruoyi-ui/src/components/Editor/EnhancedEditor.vue +++ b/ruoyi-ui/src/components/Editor/EnhancedEditor.vue @@ -78,6 +78,11 @@ export default { debug: "warn", modules: { toolbar: this.getToolbarConfig(), + clipboard: { + // 完全禁用默认的粘贴行为 + matchVisual: false, + matchers: [] + }, imageResize: this.enableImageResize ? { displayStyles: { backgroundColor: 'black', @@ -88,7 +93,7 @@ export default { modules: ['Resize', 'DisplaySize', 'Toolbar'] } : false }, - placeholder: this.placeholder, + // placeholder: this.placeholder, readOnly: this.readOnly, } }; @@ -169,7 +174,91 @@ export default { this.$emit("input", this.currentValue); } }); + + // 添加粘贴事件监听器,只保留换行的纯文本 + this.Quill.root.addEventListener('paste', (e) => { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + + // 获取剪贴板中的纯文本 + const clipboardData = e.clipboardData || window.clipboardData; + let textData = ''; + + if (clipboardData && typeof clipboardData.getData === 'function') { + textData = clipboardData.getData('text/plain'); + } + + if (textData) { + // 获取当前选择范围 + const range = this.Quill.getSelection(); + + if (range && range.length > 0) { + // 如果有选中内容,先删除选中的内容 + this.Quill.deleteText(range.index, range.length); + // 在删除的位置插入纯文本 + this.Quill.insertText(range.index, textData); + // 将光标移动到插入文本的末尾 + this.Quill.setSelection(range.index + textData.length); + } else { + // 如果没有选择范围,在光标位置插入 + const currentRange = this.Quill.getSelection(); + if (currentRange) { + this.Quill.insertText(currentRange.index, textData); + this.Quill.setSelection(currentRange.index + textData.length); + } else { + // 如果没有光标位置,在编辑器末尾插入 + this.Quill.insertText(this.Quill.getLength(), textData); + } + } + } + + return false; + }, true); }, + // 设置自定义clipboard处理,确保只粘贴纯文本 + // setupCustomClipboard() { + // const clipboard = this.Quill.getModule('clipboard'); + + // // 重写clipboard的onPaste方法 + // clipboard.onPaste = (e) => { + // // 检查是否有preventDefault方法 + // if (e && typeof e.preventDefault === 'function') { + // e.preventDefault(); + // } + + // // 获取剪贴板数据 + // const clipboardData = e.clipboardData || window.clipboardData; + // let textData = ''; + + // if (clipboardData && typeof clipboardData.getData === 'function') { + // textData = clipboardData.getData('text/plain'); + // } + + // if (textData) { + // // 只处理纯文本,不处理HTML + // const range = this.Quill.getSelection(); + // if (range && range.length > 0) { + // // 如果有选中内容,先删除选中的内容 + // this.Quill.deleteText(range.index, range.length); + // // 在删除的位置插入纯文本 + // this.Quill.insertText(range.index, textData); + // // 将光标移动到插入文本的末尾 + // this.Quill.setSelection(range.index + textData.length); + // } else { + // // 如果没有选择范围,在光标位置插入 + // const currentRange = this.Quill.getSelection(); + // if (currentRange) { + // this.Quill.insertText(currentRange.index, textData); + // this.Quill.setSelection(currentRange.index + textData.length); + // } else { + // // 如果没有光标位置,在编辑器末尾插入 + // this.Quill.insertText(this.Quill.getLength(), textData); + // } + // } + // } + // }; + // }, // 获取编辑器内容 getContent() { // 确保获取到最新的内容,包括图片调整后的状态 @@ -238,6 +327,11 @@ export default { padding: 0 8px; display: flex; align-items: center; + box-sizing: border-box; + font-family: 'Helvetica Neue','Helvetica','Arial',sans-serif; + position: sticky; + top: 0; + z-index: 99999999; } .ql-toolbar .ql-formats { @@ -472,11 +566,64 @@ export default { } /* 确保编辑器容器不会裁剪下拉框 */ -.el-dialog .enhanced-editor { - overflow: visible !important; +.el-dialog .editor-container { + overflow: auto !important; } -.el-dialog .editor-container { - overflow: visible !important; +/* 为每个编辑器实例设置递增的z-index */ +.enhanced-editor:nth-child(1) .ql-snow .ql-picker-options { + z-index: 10010 !important; +} + +.enhanced-editor:nth-child(2) .ql-snow .ql-picker-options { + z-index: 10009 !important; +} + +.enhanced-editor:nth-child(3) .ql-snow .ql-picker-options { + z-index: 10008 !important; +} + +.enhanced-editor:nth-child(4) .ql-snow .ql-picker-options { + z-index: 10007 !important; +} + +.enhanced-editor:nth-child(5) .ql-snow .ql-picker-options { + z-index: 10006 !important; +} + +.enhanced-editor:nth-child(6) .ql-snow .ql-picker-options { + z-index: 10005 !important; +} + +.enhanced-editor:nth-child(7) .ql-snow .ql-picker-options { + z-index: 10004 !important; +} + +.enhanced-editor:nth-child(8) .ql-snow .ql-picker-options { + z-index: 10003 !important; +} + +.enhanced-editor:nth-child(9) .ql-snow .ql-picker-options { + z-index: 10002 !important; +} + +.enhanced-editor:nth-child(10) .ql-snow .ql-picker-options { + z-index: 10001 !important; +} + +.enhanced-editor:nth-child(11) .ql-snow .ql-picker-options { + z-index: 10000 !important; +} + +/* 确保下拉菜单在正确的定位上下文中 */ +.ql-snow .ql-picker-options { + position: fixed !important; + z-index: 10000 !important; + background: white !important; + border: 1px solid #ccc !important; + border-radius: 4px !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important; + max-height: 200px !important; + overflow-y: auto !important; } diff --git a/ruoyi-ui/src/components/Editor/index.vue b/ruoyi-ui/src/components/Editor/index.vue index b676f8f5edee94b0c08a84dee189c3fc38d813ca..25e11c67c9f1400e7f34e74cc891d0e50b9bc1ba 100644 --- a/ruoyi-ui/src/components/Editor/index.vue +++ b/ruoyi-ui/src/components/Editor/index.vue @@ -1,14 +1,16 @@ diff --git a/ruoyi-ui/src/views/adsRecoverStation/station/index.vue b/ruoyi-ui/src/views/adsRecoverStation/station/index.vue index 2add5104c278ad284bf29abc74a3cde3bcc95a2c..a0e28650cef52f62d11cdb85b37fae9af4e78a4a 100644 --- a/ruoyi-ui/src/views/adsRecoverStation/station/index.vue +++ b/ruoyi-ui/src/views/adsRecoverStation/station/index.vue @@ -106,7 +106,7 @@ - + - + @@ -148,9 +148,9 @@ - - - + + + @@ -216,17 +216,17 @@ export default { const id = row.id || this.ids; this.$modal.confirm('确定要恢复吗?') .then(() => { - return recoverData(id); + return recoverData(id); }) .then(response => { - let message = response.msg; + let message = response.msg; console.log(message); - this.$modal.msgSuccess(message); - this.getList(); + this.$modal.msgSuccess(message); + this.getList(); }) .catch(error => { - console.error("恢复失败:", error); - this.$modal.msgError("恢复失败"); + console.error("恢复失败:", error); + this.$modal.msgError("恢复失败"); }); }, @@ -329,7 +329,7 @@ export default { /** 删除按钮操作 */ handleDelete(row) { const ids = row.id || this.ids; - this.$modal.confirm('是否确认删除行政审批回收站编号为"' + ids + '"的数据项?').then(function() { + this.$modal.confirm('是否确认删除行政审批回收站名为"' + row.item + '"的数据项?').then(function() { return delStation(ids); }).then(() => { this.getList(); diff --git a/ruoyi-ui/src/views/ai/ai/index.vue b/ruoyi-ui/src/views/ai/ai/index.vue index 36c658d9df6dd2e427e8eed99e5437c1563ca40e..f23aebfb8d6fed8a317b17398be0e7f18ff433ef 100644 --- a/ruoyi-ui/src/views/ai/ai/index.vue +++ b/ruoyi-ui/src/views/ai/ai/index.vue @@ -108,7 +108,7 @@ /> - + @@ -295,7 +295,7 @@ export default { /** 删除按钮操作 */ handleDelete(row) { const ids = row.id || this.ids; - this.$modal.confirm('是否确认删除ai编号为"' + ids + '"的数据项?').then(function() { + this.$modal.confirm('是否确认删除ai名为"' + row.title + '"的数据项?').then(function() { return delAi(ids); }).then(() => { this.getList(); diff --git a/ruoyi-ui/src/views/article/article/index.vue b/ruoyi-ui/src/views/article/article/index.vue index 46d8097329aa1e446ea93500d4512c7b9afb04b6..cfbba406d39bc31196a037fbc9c4109d2e321e82 100644 --- a/ruoyi-ui/src/views/article/article/index.vue +++ b/ruoyi-ui/src/views/article/article/index.vue @@ -9,9 +9,9 @@ @keyup.enter.native="handleQuery" /> - + - - 新增 - - - 修改 - - - 删除 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 按条件批量操作 - - 草稿箱 - + + + + + + + + + - - + + > - - + + - - - - + + + + + + - - + + - + - + - + @@ -226,7 +226,7 @@ export default { /** 删除按钮操作 */ handleDelete(row) { const ids = row.id || this.ids; - this.$modal.confirm('是否确认删除审批类型编号为"' + ids + '"的数据项?').then(function() { + this.$modal.confirm('是否确认删除审批类型名为"' + row.kind + '"的数据项?').then(function() { return delKind(ids); }).then(() => { this.getList(); diff --git a/ruoyi-ui/src/views/leader/leader/index.vue b/ruoyi-ui/src/views/leader/leader/index.vue index 7876e621899542858e87fe21cf7c58a838e4f552..d5df886f2d515d373790ffd6e7707f39e6b0be58 100644 --- a/ruoyi-ui/src/views/leader/leader/index.vue +++ b/ruoyi-ui/src/views/leader/leader/index.vue @@ -83,6 +83,17 @@ + + + - + - + +