diff --git a/pom.xml b/pom.xml index 0ce0784e81..3d64ab53a9 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ https://github.com/YunaiV/ruoyi-vue-pro - 2025.09-jdk8-SNAPSHOT + 2025.10-jdk8-SNAPSHOT 1.8 ${java.version} diff --git a/sql/tools/convertor.py b/sql/tools/convertor.py index d286d3b238..b54d1337c8 100644 --- a/sql/tools/convertor.py +++ b/sql/tools/convertor.py @@ -6,12 +6,12 @@ Author: dhb52 (https://gitee.com/dhb52) pip install simple-ddl-parser or with uv -uv run --with simple-ddl-parser convertor.py postgres > ../postgresql/ruoyi-vue-pro.sql 239ms  四 5/22 21:03:16 2025 -uv run --with simple-ddl-parser convertor.py sqlserver > ../sqlserver/ruoyi-vue-pro.sql -uv run --with simple-ddl-parser convertor.py kingbase > ../kingbase/ruoyi-vue-pro.sql -uv run --with simple-ddl-parser convertor.py opengauss > ../opengauss/ruoyi-vue-pro.sql -uv run --with simple-ddl-parser convertor.py oracle > ../oracle/ruoyi-vue-pro.sql -uv run --with simple-ddl-parser convertor.py dm8 > ../dm/ruoyi-vue-pro-dm8.sql +uv run --with simple-ddl-parser convertor.py postgres ../mysql/ruoyi-vue-pro.sql > ../postgresql/ruoyi-vue-pro.sql +uv run --with simple-ddl-parser convertor.py sqlserver ../mysql/ruoyi-vue-pro.sql > ../sqlserver/ruoyi-vue-pro.sql +uv run --with simple-ddl-parser convertor.py kingbase ../mysql/ruoyi-vue-pro.sql > ../kingbase/ruoyi-vue-pro.sql +uv run --with simple-ddl-parser convertor.py opengauss ../mysql/ruoyi-vue-pro.sql > ../opengauss/ruoyi-vue-pro.sql +uv run --with simple-ddl-parser convertor.py oracle ../mysql/ruoyi-vue-pro.sql > ../oracle/ruoyi-vue-pro.sql +uv run --with simple-ddl-parser convertor.py dm8 ../mysql/ruoyi-vue-pro.sql > ../dm/ruoyi-vue-pro-dm8.sql """ import argparse @@ -24,6 +24,9 @@ from typing import Dict, Generator, Optional, Tuple, Union from simple_ddl_parser import DDLParser +# 避免 Windows 系统使用默认的 GBK 编码 +sys.stdout = open(sys.stdout.fileno(), mode='w', encoding='utf-8', buffering=1) + PREAMBLE = """/* Yudao Database Transfer Tool @@ -919,9 +922,15 @@ def main(): help="目标数据库类型", choices=["postgres", "oracle", "sqlserver", "dm8", "kingbase", "opengauss"], ) + parser.add_argument( + "path", + type=str, + help="源数据库脚本路径", + default="../mysql/ruoyi-vue-pro.sql" + ) args = parser.parse_args() - sql_file = pathlib.Path("../mysql/ruoyi-vue-pro.sql").resolve().as_posix() + sql_file = pathlib.Path(args.path).resolve().as_posix() convertor = None if args.type == "postgres": convertor = PostgreSQLConvertor(sql_file) diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 98b5641bde..d2e9e12839 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -14,7 +14,7 @@ https://github.com/YunaiV/ruoyi-vue-pro - 2025.09-jdk8-SNAPSHOT + 2025.10-jdk8-SNAPSHOT 1.6.0 5.3.39 @@ -27,7 +27,7 @@ 1.2.27 3.5.19 - 3.5.12 + 3.5.14 1.5.4 4.3.1 3.0.6 @@ -63,7 +63,7 @@ 2.14.5 3.11.1 3.18.0 - 0.1.55 + 2.27.3 2.9.3 2.7.0 3.0.6 @@ -535,7 +535,7 @@ ${commons-net.version} - com.jcraft + com.github.mwiede jsch ${jsch.version} diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java index 4cbb91c2cc..775447a060 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; +import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; @@ -80,8 +81,8 @@ public class YudaoMybatisAutoConfiguration { throw new IllegalArgumentException(StrUtil.format("DbType{} 找不到合适的 IKeyGenerator 实现类", dbType)); } - @Bean - public JacksonTypeHandler jacksonTypeHandler(List objectMappers) { + @Bean // 特殊:返回结果使用 Object 而不用 JacksonTypeHandler 的原因,避免因为 JacksonTypeHandler 被 mybatis 全局使用! + public Object jacksonTypeHandler(List objectMappers) { // 特殊:设置 JacksonTypeHandler 的 ObjectMapper! ObjectMapper objectMapper = CollUtil.getFirst(objectMappers); if (objectMapper == null) { diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java index 3929b7106b..460ea78116 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java @@ -60,6 +60,12 @@ public enum DbTypeEnum { * 人大金仓 */ KINGBASE_ES(DbType.KINGBASE_ES, "KingbaseES", "POSITION('#{value}' IN #{column}) <> 0"), + + /** + * OceanBase + */ + OCEAN_BASE(DbType.OCEAN_BASE, "OceanBase", "FIND_IN_SET('#{value}', #{column}) <> 0") + ; public static final Map MAP_BY_NAME = Arrays.stream(values()) diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java index 60fbebfabb..2a47af6f9b 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.framework.swagger.config; +import com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; @@ -12,6 +13,7 @@ import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; import org.springdoc.core.*; import org.springdoc.core.customizers.OpenApiBuilderCustomizer; +import org.springdoc.core.customizers.OperationCustomizer; import org.springdoc.core.customizers.ServerBaseUrlCustomizer; import org.springdoc.core.providers.JavadocProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -39,7 +41,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_ * * @author 芋道源码 */ -@AutoConfiguration +@AutoConfiguration(before = Knife4jAutoConfiguration.class) // before 原因,保证覆写的 Knife4jOpenApiCustomizer 先生效!相关 https://github.com/YunaiV/ruoyi-vue-pro/issues/954 讨论 @ConditionalOnClass({OpenAPI.class}) @EnableConfigurationProperties(SwaggerProperties.class) @ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用 @@ -123,6 +125,7 @@ public class YudaoSwaggerAutoConfiguration { .addOperationCustomizer((operation, handlerMethod) -> operation .addParametersItem(buildTenantHeaderParameter()) .addParametersItem(buildSecurityHeaderParameter())) + .addOperationCustomizer(buildOperationIdCustomizer()) .build(); } @@ -154,5 +157,26 @@ public class YudaoSwaggerAutoConfiguration { .schema(new StringSchema()._default("Bearer test1").name(HEADER_TENANT_ID).description("认证 Token")); // 默认:使用用户编号为 1 } + /** + * 核心:自定义OperationId生成规则,组合「类名前缀 + 方法名」 + * + * @see app-api 前缀不生效,都是使用 admin-api + */ + private static OperationCustomizer buildOperationIdCustomizer() { + return (operation, handlerMethod) -> { + // 1. 获取控制器类名(如 UserController) + String className = handlerMethod.getBeanType().getSimpleName(); + // 2. 提取类名前缀(去除 Controller 后缀,如 UserController -> User) + String classPrefix = className.replaceAll("Controller$", ""); + // 3. 获取方法名(如 list) + String methodName = handlerMethod.getMethod().getName(); + // 4. 组合生成 operationId(如 User_list) + String operationId = classPrefix + "_" + methodName; + // 5. 设置自定义 operationId + operation.setOperationId(operationId); + return operation; + }; + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java index 65defd1318..cea018cbe0 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.framework.web.config; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi; import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter; @@ -7,11 +8,13 @@ import cn.iocoder.yudao.framework.web.core.filter.DemoFilter; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; +import com.google.common.collect.Maps; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.servlet.FilterRegistrationBean; @@ -22,40 +25,58 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; -import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import javax.annotation.Resource; import javax.servlet.Filter; +import java.util.Map; +import java.util.function.Predicate; @AutoConfiguration @EnableConfigurationProperties(WebProperties.class) -public class YudaoWebAutoConfiguration implements WebMvcConfigurer { +public class YudaoWebAutoConfiguration { - @Resource - private WebProperties webProperties; /** * 应用名 */ @Value("${spring.application.name}") private String applicationName; - @Override - public void configurePathMatch(PathMatchConfigurer configurer) { - configurePathMatch(configurer, webProperties.getAdminApi()); - configurePathMatch(configurer, webProperties.getAppApi()); - } + @Bean + public WebMvcRegistrations webMvcRegistrations(WebProperties webProperties) { + return new WebMvcRegistrations() { - /** - * 设置 API 前缀,仅仅匹配 controller 包下的 - * - * @param configurer 配置 - * @param api API 配置 - */ - private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) { - AntPathMatcher antPathMatcher = new AntPathMatcher("."); - configurer.addPathPrefix(api.getPrefix(), clazz -> clazz.isAnnotationPresent(RestController.class) - && antPathMatcher.match(api.getController(), clazz.getPackage().getName())); // 仅仅匹配 controller 包 + @Override + public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { + RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping(); + // 实例化时就带上前缀 + mapping.setPathPrefixes(buildPathPrefixes(webProperties)); + return mapping; + } + + /** + * 构建 prefix → 匹配条件的映射 + */ + private Map>> buildPathPrefixes(WebProperties webProperties) { + AntPathMatcher antPathMatcher = new AntPathMatcher("."); + Map>> pathPrefixes = Maps.newLinkedHashMapWithExpectedSize(2); + putPathPrefix(pathPrefixes, webProperties.getAdminApi(), antPathMatcher); + putPathPrefix(pathPrefixes, webProperties.getAppApi(), antPathMatcher); + return pathPrefixes; + } + + /** + * 设置 API 前缀,仅仅匹配 controller 包下的 + */ + private void putPathPrefix(Map>> pathPrefixes, WebProperties.Api api, AntPathMatcher matcher) { + if (api == null || StrUtil.isEmpty(api.getPrefix())) { + return; + } + pathPrefixes.put(api.getPrefix(), // api 前缀 + clazz -> clazz.isAnnotationPresent(RestController.class) + && matcher.match(api.getController(), clazz.getPackage().getName())); + } + + }; } @Bean diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessPrintDataRespVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessPrintDataRespVO.java new file mode 100644 index 0000000000..8f85da966a --- /dev/null +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessPrintDataRespVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 流程实例的打印数据 Response VO") +@Data +public class BpmProcessPrintDataRespVO { + + @Schema(description = "流程实例数据") + private BpmProcessInstanceRespVO processInstance; + + @Schema(description = "是否开启自定义打印模板", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean printTemplateEnable; + + @Schema(description = "自定义打印模板 HTML") + private String printTemplateHtml; + + @Schema(description = "审批任务列表") + private List tasks; + + @Schema(description = "流程任务") + @Data + public static class Task { + + @Schema(description = "流程任务的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String id; + + @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + private String name; + + @Schema(description = "签名 URL", example = "https://www.iocoder.cn/sign.png") + private String signPicUrl; + + @Schema(description = "任务描述", requiredMode = Schema.RequiredMode.REQUIRED) + private String description; // 该字段由后端拼接 + + } + +} diff --git a/yudao-module-infra/pom.xml b/yudao-module-infra/pom.xml index cf8ca1ac90..4cd4d7799e 100644 --- a/yudao-module-infra/pom.xml +++ b/yudao-module-infra/pom.xml @@ -94,7 +94,7 @@ commons-net - com.jcraft + com.github.mwiede jsch diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index c55c3c9621..2789242c27 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -26,6 +26,7 @@ import javax.annotation.security.PermitAll; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; +import java.nio.charset.StandardCharsets; import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -99,8 +100,10 @@ public class FileController { if (StrUtil.isEmpty(path)) { throw new IllegalArgumentException("结尾的 path 路径必须传递"); } - // 解码,解决中文路径的问题 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/ - path = URLUtil.decode(path); + // 解码,解决中文路径的问题 + // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/ + // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1432/ + path = URLUtil.decode(path, StandardCharsets.UTF_8, false); // 读取内容 byte[] content = fileService.getFileContent(configId, path); diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java index 0ff9e56a9c..7f55507e42 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java @@ -21,9 +21,8 @@ public enum CodegenFrontTypeEnum { VUE3_VBEN5_ANTD_SCHEMA(40), // Vue3 VBEN5 + ANTD + schema 模版 VUE3_VBEN5_ANTD_GENERAL(41), // Vue3 VBEN5 + ANTD 标准模版 - // TODO @puhui999::50、51 会好点; - VUE3_VBEN5_EP_SCHEMA(42), // Vue3 VBEN5 + EP + schema 模版 - VUE3_VBEN5_EP_GENERAL(43), // Vue3 VBEN5 + EP 标准模版 + VUE3_VBEN5_EP_SCHEMA(50), // Vue3 VBEN5 + EP + schema 模版 + VUE3_VBEN5_EP_GENERAL(51), // Vue3 VBEN5 + EP 标准模版 ; /** diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClient.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClient.java index 4207eb7e15..93bff27ece 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClient.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClient.java @@ -4,6 +4,7 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ftp.Ftp; +import cn.hutool.extra.ftp.FtpConfig; import cn.hutool.extra.ftp.FtpException; import cn.hutool.extra.ftp.FtpMode; import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient; @@ -18,6 +19,15 @@ import java.io.ByteArrayOutputStream; */ public class FtpFileClient extends AbstractFileClient { + /** + * 连接超时时间,单位:毫秒 + */ + private static final Long CONNECTION_TIMEOUT = 3000L; + /** + * 读写超时时间,单位:毫秒 + */ + private static final Long SO_TIMEOUT = 10000L; + private Ftp ftp; public FtpFileClient(Long id, FtpFileClientConfig config) { @@ -26,9 +36,12 @@ public class FtpFileClient extends AbstractFileClient { @Override protected void doInit() { - // 初始化 Ftp 对象 - this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(), - CharsetUtil.CHARSET_UTF_8, null, null, FtpMode.valueOf(config.getMode())); + // 初始化 Ftp 对象:https://gitee.com/zhijiantianya/yudao-cloud/pulls/207/ + FtpConfig ftpConfig = new FtpConfig(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(), + CharsetUtil.CHARSET_UTF_8, null, null); + ftpConfig.setConnectionTimeout(CONNECTION_TIMEOUT); + ftpConfig.setSoTimeout(SO_TIMEOUT); + this.ftp = new Ftp(ftpConfig, FtpMode.valueOf(config.getMode())); } @Override @@ -72,4 +85,4 @@ public class FtpFileClient extends AbstractFileClient { ftp.reconnectIfTimeout(); } -} \ No newline at end of file +} diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClient.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClient.java index 000cbd10b3..788325f6c2 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClient.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClient.java @@ -1,9 +1,14 @@ package cn.iocoder.yudao.module.infra.framework.file.core.client.sftp; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.ftp.FtpConfig; +import cn.hutool.extra.ssh.JschRuntimeException; import cn.hutool.extra.ssh.Sftp; import cn.iocoder.yudao.framework.common.util.io.FileUtils; import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient; +import com.jcraft.jsch.JSch; import java.io.File; @@ -14,6 +19,20 @@ import java.io.File; */ public class SftpFileClient extends AbstractFileClient { + /** + * 连接超时时间,单位:毫秒 + */ + private static final Long CONNECTION_TIMEOUT = 3000L; + /** + * 读写超时时间,单位:毫秒 + */ + private static final Long SO_TIMEOUT = 10000L; + + static { + // 某些旧的 sftp 服务器仅支持 ssh-dss 协议,该协议并不安全,默认不支持该协议,按需添加 + JSch.setConfig("server_host_key", JSch.getConfig("server_host_key") + ",ssh-dss"); + } + private Sftp sftp; public SftpFileClient(Long id, SftpFileClientConfig config) { @@ -22,18 +41,27 @@ public class SftpFileClient extends AbstractFileClient { @Override protected void doInit() { - // 初始化 Ftp 对象 - this.sftp = new Sftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword()); + // 初始化 Sftp 对象 + FtpConfig ftpConfig = new FtpConfig(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(), + CharsetUtil.CHARSET_UTF_8, null, null); + ftpConfig.setConnectionTimeout(CONNECTION_TIMEOUT); + ftpConfig.setSoTimeout(SO_TIMEOUT); + this.sftp = new Sftp(ftpConfig); } @Override public String upload(byte[] content, String path, String type) { // 执行写入 String filePath = getFilePath(path); + String fileName = FileUtil.getName(filePath); + String dir = StrUtil.removeSuffix(filePath, fileName); File file = FileUtils.createTempFile(content); reconnectIfTimeout(); - sftp.mkDirs(FileUtil.getParent(filePath, 1)); // 需要创建父目录,不然会报错 - sftp.upload(filePath, file); + sftp.mkDirs(dir); // 需要创建父目录,不然会报错 + boolean success = sftp.upload(filePath, file); + if (!success) { + throw new JschRuntimeException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath)); + } // 拼接返回路径 return super.formatFileUrl(config.getDomain(), path); } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java index b3826da2dc..c4ce19ed64 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java @@ -343,7 +343,7 @@ public class CodegenEngine { filePath = formatFilePath(filePath, bindingMap); String content = templateEngine.getTemplate(vmPath).render(bindingMap); // 格式化代码 - content = prettyCode(content); + content = prettyCode(content, vmPath); result.put(filePath, content); } @@ -383,11 +383,14 @@ public class CodegenEngine { * 如果不处理,Vue 的 Pretty 格式校验可能会报错 * * @param content 格式化前的代码 + * @param vmPath 模板路径 * @return 格式化后的代码 */ - private String prettyCode(String content) { - // Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错 - content = content.replaceAll(",\n}", "\n}").replaceAll(",\n }", "\n }"); + private String prettyCode(String content, String vmPath) { + // Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错(需要排除 vben5) + if (!StrUtil.contains(vmPath, "vben5")) { + content = content.replaceAll(",\n}", "\n}").replaceAll(",\n }", "\n }"); + } // Vue 界面:去除多的 dateFormatter,只有一个的情况下,说明没使用到 if (StrUtil.count(content, "dateFormatter") == 1) { content = StrUtils.removeLineContains(content, "dateFormatter"); diff --git a/yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm index a94cab5a59..29df64de16 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm @@ -217,7 +217,7 @@ const handleDeleteBatch = async () => { const checkedIds = ref([]) const handleRowCheckboxChange = (records: ${subSimpleClassName}[]) => { - checkedIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id!); } #end #end diff --git a/yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm index dfb97804ce..fb7485d6d5 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm @@ -374,7 +374,7 @@ const handleDeleteBatch = async () => { const checkedIds = ref([]) const handleRowCheckboxChange = (records: ${simpleClassName}[]) => { - checkedIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id!); } #end diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/api/api.ts.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/api/api.ts.vm index 090b14845b..c3691a8b73 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/api/api.ts.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/api/api.ts.vm @@ -98,7 +98,7 @@ export function delete${simpleClassName}List(ids: number[]) { /** 导出${table.classComment} */ export function export${simpleClassName}(params: any) { - return requestClient.download('${baseURL}/export-excel', params); + return requestClient.download('${baseURL}/export-excel', { params }); } ## 特殊:主子表专属逻辑 diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm index 90c0a6e9c5..06dc0c86c1 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm @@ -1,12 +1,15 @@