diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java index 5b7808baf..3f662a85e 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java @@ -49,8 +49,15 @@ public class HttpUtils { return builder.build(); } - private String append(String base, Map query, boolean fragment) { - return append(base, query, null, fragment); + public static String removeUrlQuery(String url) { + if (!StrUtil.contains(url, '?')) { + return url; + } + UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset()); + // 移除 query、fragment + builder.setQuery(null); + builder.setFragment(null); + return builder.build(); } /** diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java index ac33ba8ef..56f51d91d 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.SortingField; import cn.iocoder.yudao.framework.mybatis.core.enums.DbTypeEnum; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.core.toolkit.StringPool; @@ -47,16 +48,36 @@ public class MyBatisUtils { return page; } + @SuppressWarnings("PatternVariableCanBeUsed") public static void addOrder(Wrapper wrapper, Collection sortingFields) { if (CollUtil.isEmpty(sortingFields)) { return; } - QueryWrapper query = (QueryWrapper) wrapper; - for (SortingField sortingField : sortingFields) { - query.orderBy(true, - SortingField.ORDER_ASC.equals(sortingField.getOrder()), - StrUtil.toUnderlineCase(sortingField.getField())); + if (wrapper instanceof QueryWrapper) { + QueryWrapper query = (QueryWrapper) wrapper; + for (SortingField sortingField : sortingFields) { + query.orderBy(true, + SortingField.ORDER_ASC.equals(sortingField.getOrder()), + StrUtil.toUnderlineCase(sortingField.getField())); + } + } else if (wrapper instanceof LambdaQueryWrapper) { + // LambdaQueryWrapper 不直接支持字符串字段排序,使用 last 方法拼接 ORDER BY + LambdaQueryWrapper lambdaQuery = (LambdaQueryWrapper) wrapper; + StringBuilder orderBy = new StringBuilder(); + for (SortingField sortingField : sortingFields) { + if (StrUtil.isNotEmpty(orderBy)) { + orderBy.append(", "); + } + orderBy.append(StrUtil.toUnderlineCase(sortingField.getField())) + .append(" ") + .append(SortingField.ORDER_ASC.equals(sortingField.getOrder()) ? "ASC" : "DESC"); + } + lambdaQuery.last("ORDER BY " + orderBy); + // 另外个思路:https://blog.csdn.net/m0_59084856/article/details/138450913 + } else { + throw new IllegalArgumentException("Unsupported wrapper type: " + wrapper.getClass().getName()); } + } /** diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java index 491474c3c..dc74078c8 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java @@ -32,6 +32,7 @@ import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.servlet.NoHandlerFoundException; import javax.servlet.http.HttpServletRequest; @@ -92,6 +93,9 @@ public class GlobalExceptionHandler { if (ex instanceof ValidationException) { return validationException((ValidationException) ex); } + if (ex instanceof MaxUploadSizeExceededException) { + return maxUploadSizeExceededExceptionHandler((MaxUploadSizeExceededException) ex); + } if (ex instanceof NoHandlerFoundException) { return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex); } @@ -107,9 +111,6 @@ public class GlobalExceptionHandler { if (ex instanceof AccessDeniedException) { return accessDeniedExceptionHandler(request, (AccessDeniedException) ex); } - if (ex instanceof UncheckedExecutionException && ex.getCause() != ex) { - return allExceptionHandler(request, ex.getCause()); - } return defaultExceptionHandler(request, ex); } @@ -209,6 +210,14 @@ public class GlobalExceptionHandler { return CommonResult.error(BAD_REQUEST); } + /** + * 处理上传文件过大异常 + */ + @ExceptionHandler(MaxUploadSizeExceededException.class) + public CommonResult maxUploadSizeExceededExceptionHandler(MaxUploadSizeExceededException ex) { + return CommonResult.error(BAD_REQUEST.getCode(), "上传文件过大,请调整后重试"); + } + /** * 处理 SpringMVC 请求地址不存在 * @@ -296,6 +305,12 @@ public class GlobalExceptionHandler { */ @ExceptionHandler(value = Exception.class) public CommonResult defaultExceptionHandler(HttpServletRequest req, Throwable ex) { + // 特殊:如果是 ServiceException 的异常,则直接返回 + // 例如说:https://gitee.com/zhijiantianya/yudao-cloud/issues/ICSSRM、https://gitee.com/zhijiantianya/yudao-cloud/issues/ICT6FM + if (ex.getCause() != null && ex.getCause() instanceof ServiceException) { + return serviceExceptionHandler((ServiceException) ex.getCause()); + } + // 情况一:处理表不存在的异常 CommonResult tableNotExistsResult = handleTableNotExists(ex); if (tableNotExistsResult != null) { diff --git a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApi.java b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApi.java index 2a9955577..a3d721115 100644 --- a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApi.java +++ b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApi.java @@ -6,8 +6,10 @@ import cn.iocoder.yudao.module.infra.enums.ApiConstants; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import javax.validation.Valid; import javax.validation.constraints.NotEmpty; @@ -57,4 +59,16 @@ public interface FileApi { @Operation(summary = "保存文件,并返回文件的访问路径") CommonResult createFile(@Valid @RequestBody FileCreateReqDTO createReqDTO); + /** + * 生成文件预签名地址,用于读取 + * + * @param url 完整的文件访问地址 + * @param expirationSeconds 访问有效期,单位秒 + * @return 文件预签名地址 + */ + @GetMapping(PREFIX + "/presigned-url") + @Operation(summary = "生成文件预签名地址,用于读取") + CommonResult presignGetUrl(@NotEmpty(message = "URL 不能为空") @RequestParam("url") String url, + Integer expirationSeconds); + } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java index d7232685b..91c3651dd 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java @@ -23,4 +23,9 @@ public class FileApiImpl implements FileApi { createReqDTO.getDirectory(), createReqDTO.getType())); } + @Override + public CommonResult presignGetUrl(String url, Integer expirationSeconds) { + return success(fileService.presignGetUrl(url, expirationSeconds)); + } + } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index 7c83bbf82..7aed968e3 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -51,7 +51,7 @@ public class FileController { } @GetMapping("/presigned-url") - @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") + @Operation(summary = "获取文件预签名地址(上传)", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") @Parameters({ @Parameter(name = "name", description = "文件名称", required = true), @Parameter(name = "directory", description = "文件目录") @@ -59,7 +59,7 @@ public class FileController { public CommonResult getFilePresignedUrl( @RequestParam("name") String name, @RequestParam(value = "directory", required = false) String directory) { - return success(fileService.getFilePresignedUrl(name, directory)); + return success(fileService.presignPutUrl(name, directory)); } @PostMapping("/create") diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java index 47dcde7cf..7e354fa05 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java @@ -42,7 +42,7 @@ public class AppFileController { } @GetMapping("/presigned-url") - @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") + @Operation(summary = "获取文件预签名地址(上传)", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") @Parameters({ @Parameter(name = "name", description = "文件名称", required = true), @Parameter(name = "directory", description = "文件目录") @@ -50,7 +50,7 @@ public class AppFileController { public CommonResult getFilePresignedUrl( @RequestParam("name") String name, @RequestParam(value = "directory", required = false) String directory) { - return success(fileService.getFilePresignedUrl(name, directory)); + return success(fileService.presignPutUrl(name, directory)); } @PostMapping("/create") diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/FileClient.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/FileClient.java index 053b3c510..cf1cd620a 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/FileClient.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/FileClient.java @@ -1,7 +1,5 @@ package cn.iocoder.yudao.module.infra.framework.file.core.client; -import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO; - /** * 文件客户端 * @@ -42,13 +40,26 @@ public interface FileClient { */ byte[] getContent(String path) throws Exception; + // ========== 文件签名,目前仅 S3 支持 ========== + /** - * 获得文件预签名地址 + * 获得文件预签名地址,用于上传 * * @param path 相对路径 * @return 文件预签名地址 */ - default FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception { + default String presignPutUrl(String path) { + throw new UnsupportedOperationException("不支持的操作"); + } + + /** + * 生成文件预签名地址,用于读取 + * + * @param url 完整的文件访问地址 + * @param expirationSeconds 访问有效期,单位秒 + * @return 文件预签名地址 + */ + default String presignGetUrl(String url, Integer expirationSeconds) { throw new UnsupportedOperationException("不支持的操作"); } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/local/LocalFileClient.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/local/LocalFileClient.java index 7fa2a7ea9..6e5c0229b 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/local/LocalFileClient.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/local/LocalFileClient.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.infra.framework.file.core.client.local; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient; import java.io.File; @@ -38,7 +39,14 @@ public class LocalFileClient extends AbstractFileClient { @Override public byte[] getContent(String path) { String filePath = getFilePath(path); - return FileUtil.readBytes(filePath); + try { + return FileUtil.readBytes(filePath); + } catch (IORuntimeException ex) { + if (ex.getMessage().startsWith("File not exist:")) { + return null; + } + throw ex; + } } private String getFilePath(String path) { diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/FilePresignedUrlRespDTO.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/FilePresignedUrlRespDTO.java deleted file mode 100644 index 6a1258e9e..000000000 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/FilePresignedUrlRespDTO.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.infra.framework.file.core.client.s3; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * 文件预签名地址 Response DTO - * - * @author owen - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class FilePresignedUrlRespDTO { - - /** - * 文件上传 URL(用于上传) - * - * 例如说: - */ - private String uploadUrl; - - /** - * 文件 URL(用于读取、下载等) - */ - private String url; - -} diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java index a33f0d738..8e7f74611 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java @@ -1,8 +1,10 @@ package cn.iocoder.yudao.module.infra.framework.file.core.client.s3; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; @@ -15,9 +17,11 @@ import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest; import java.net.URI; +import java.net.URL; import java.time.Duration; /** @@ -27,6 +31,8 @@ import java.time.Duration; */ public class S3FileClient extends AbstractFileClient { + private static final Duration EXPIRATION_DEFAULT = Duration.ofHours(24); + private S3Client client; private S3Presigner presigner; @@ -75,7 +81,7 @@ public class S3FileClient extends AbstractFileClient { // 上传文件 client.putObject(putRequest, RequestBody.fromBytes(content)); // 拼接返回路径 - return config.getDomain() + "/" + path; + return presignGetUrl(path, null); } @Override @@ -97,23 +103,33 @@ public class S3FileClient extends AbstractFileClient { } @Override - public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) { - Duration expiration = Duration.ofHours(24); - return new FilePresignedUrlRespDTO(getPresignedUrl(path, expiration), config.getDomain() + "/" + path); + public String presignPutUrl(String path) { + return presigner.presignPutObject(PutObjectPresignRequest.builder() + .signatureDuration(EXPIRATION_DEFAULT) + .putObjectRequest(b -> b.bucket(config.getBucket()).key(path)).build()) + .url().toString(); } - /** - * 生成动态的预签名上传 URL - * - * @param path 相对路径 - * @param expiration 过期时间 - * @return 生成的上传 URL - */ - private String getPresignedUrl(String path, Duration expiration) { - return presigner.presignPutObject(PutObjectPresignRequest.builder() - .signatureDuration(expiration) - .putObjectRequest(b -> b.bucket(config.getBucket()).key(path)) - .build()).url().toString(); + @Override + public String presignGetUrl(String url, Integer expirationSeconds) { + // 1. 将 url 转换为 path + String path = StrUtil.removePrefix(url, config.getDomain() + "/"); + path = HttpUtils.removeUrlQuery(path); + + // 2.1 情况一:公开访问:无需签名 + // 考虑到老版本的兼容,所以必须是 config.getEnablePublicAccess() 为 false 时,才进行签名 + if (!BooleanUtil.isFalse(config.getEnablePublicAccess())) { + return config.getDomain() + "/" + path; + } + + // 2.2 情况二:私有访问:生成 GET 预签名 URL + String finalPath = path; + Duration expiration = expirationSeconds != null ? Duration.ofSeconds(expirationSeconds) : EXPIRATION_DEFAULT; + URL signedUrl = presigner.presignGetObject(GetObjectPresignRequest.builder() + .signatureDuration(expiration) + .getObjectRequest(b -> b.bucket(config.getBucket()).key(finalPath)).build()) + .url(); + return signedUrl.toString(); } /** diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClientConfig.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClientConfig.java index c4a402b3b..216197964 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClientConfig.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClientConfig.java @@ -6,8 +6,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.hibernate.validator.constraints.URL; -import javax.validation.constraints.AssertTrue; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; /** * S3 文件客户端的配置类 @@ -73,6 +73,15 @@ public class S3FileClientConfig implements FileClientConfig { @NotNull(message = "enablePathStyleAccess 不能为空") private Boolean enablePathStyleAccess; + /** + * 是否公开访问 + * + * true:公开访问,所有人都可以访问 + * false:私有访问,只有配置的 accessKey 才可以访问 + */ + @NotNull(message = "是否公开访问不能为空") + private Boolean enablePublicAccess; + @SuppressWarnings("RedundantIfStatement") @AssertTrue(message = "domain 不能为空") @JsonIgnore diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java index 251f3c927..29cfb40eb 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java @@ -80,9 +80,15 @@ public class FileTypeUtils { */ public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException { // 设置 header 和 contentType - response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename)); String contentType = getMineType(content, filename); response.setContentType(contentType); + // 设置内容显示、下载文件名:https://www.cnblogs.com/wq-9/articles/12165056.html + if (StrUtil.containsIgnoreCase(contentType, "image/")) { + // 参见 https://github.com/YunaiV/ruoyi-vue-pro/issues/692 讨论 + response.setHeader("Content-Disposition", "inline;filename=" + HttpUtils.encodeUtf8(filename)); + } else { + response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename)); + } // 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题 if (StrUtil.containsIgnoreCase(contentType, "video")) { response.setHeader("Content-Length", String.valueOf(content.length)); diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java index ae1f7e556..81b5ebe0b 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java @@ -38,14 +38,22 @@ public interface FileService { String name, String directory, String type); /** - * 生成文件预签名地址信息 + * 生成文件预签名地址信息,用于上传 * * @param name 文件名 * @param directory 目录 * @return 预签名地址信息 */ - FilePresignedUrlRespVO getFilePresignedUrl(@NotEmpty(message = "文件名不能为空") String name, - String directory); + FilePresignedUrlRespVO presignPutUrl(@NotEmpty(message = "文件名不能为空") String name, + String directory); + /** + * 生成文件预签名地址信息,用于读取 + * + * @param url 完整的文件访问地址 + * @param expirationSeconds 访问有效期,单位秒 + * @return 文件预签名地址 + */ + String presignGetUrl(String url, Integer expirationSeconds); /** * 创建文件 diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java index 11932c31f..f6d0cb726 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java @@ -6,6 +6,7 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.digest.DigestUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; @@ -13,7 +14,6 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresigned import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient; -import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO; import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils; import com.google.common.annotations.VisibleForTesting; import javax.annotation.Resource; @@ -126,19 +126,27 @@ public class FileServiceImpl implements FileService { @Override @SneakyThrows - public FilePresignedUrlRespVO getFilePresignedUrl(String name, String directory) { + public FilePresignedUrlRespVO presignPutUrl(String name, String directory) { // 1. 生成上传的 path,需要保证唯一 String path = generateUploadPath(name, directory); // 2. 获取文件预签名地址 FileClient fileClient = fileConfigService.getMasterFileClient(); - FilePresignedUrlRespDTO presignedObjectUrl = fileClient.getPresignedObjectUrl(path); - return BeanUtils.toBean(presignedObjectUrl, FilePresignedUrlRespVO.class, - object -> object.setConfigId(fileClient.getId()).setPath(path)); + String uploadUrl = fileClient.presignPutUrl(path); + String visitUrl = fileClient.presignGetUrl(path, null); + return new FilePresignedUrlRespVO().setConfigId(fileClient.getId()) + .setPath(path).setUploadUrl(uploadUrl).setUrl(visitUrl); + } + + @Override + public String presignGetUrl(String url, Integer expirationSeconds) { + FileClient fileClient = fileConfigService.getMasterFileClient(); + return fileClient.presignGetUrl(url, expirationSeconds); } @Override public Long createFile(FileCreateReqVO createReqVO) { + createReqVO.setUrl(HttpUtils.removeUrlQuery(createReqVO.getUrl())); // 目的:移除私有桶情况下,URL 的签名参数 FileDO file = BeanUtils.toBean(createReqVO, FileDO.class); fileMapper.insert(file); return file.getId(); diff --git a/yudao-module-infra/yudao-module-infra-server/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/local/LocalFileClientTest.java b/yudao-module-infra/yudao-module-infra-server/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/local/LocalFileClientTest.java index 7c622a530..6bc8c7bfe 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/local/LocalFileClientTest.java +++ b/yudao-module-infra/yudao-module-infra-server/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/local/LocalFileClientTest.java @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileC import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; + public class LocalFileClientTest { @Test @@ -26,4 +28,18 @@ public class LocalFileClientTest { client.delete(path); } + @Test + @Disabled + public void testGetContent_notFound() { + // 创建客户端 + LocalFileClientConfig config = new LocalFileClientConfig(); + config.setDomain("http://127.0.0.1:48080"); + config.setBasePath("/Users/yunai/file_test"); + LocalFileClient client = new LocalFileClient(0L, config); + client.init(); + // 上传文件 + byte[] content = client.getContent(randomString()); + System.out.println(); + } + } diff --git a/yudao-module-infra/yudao-module-infra-server/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/s3/S3FileClientTest.java b/yudao-module-infra/yudao-module-infra-server/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/s3/S3FileClientTest.java index 1933e9858..981971b9f 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/s3/S3FileClientTest.java +++ b/yudao-module-infra/yudao-module-infra-server/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/s3/S3FileClientTest.java @@ -5,11 +5,11 @@ import cn.hutool.core.util.IdUtil; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClient; import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig; +import jakarta.validation.Validation; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import javax.validation.Validation; - +@SuppressWarnings("resource") public class S3FileClientTest { @Test @@ -71,6 +71,7 @@ public class S3FileClientTest { config.setAccessSecret("kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP"); config.setBucket("ruoyi-vue-pro"); config.setDomain("http://test.yudao.iocoder.cn"); // 如果有自定义域名,则可以设置。http://static.yudao.iocoder.cn + config.setEnablePathStyleAccess(false); // 默认上海的 endpoint config.setEndpoint("s3-cn-south-1.qiniucs.com"); @@ -78,6 +79,32 @@ public class S3FileClientTest { testExecuteUpload(config); } + @Test + @Disabled // 七牛云存储(读私有桶),如果要集成测试,可以注释本行 + public void testQiniu_privateGet() { + S3FileClientConfig config = new S3FileClientConfig(); + // 配置成你自己的 +// config.setAccessKey(System.getenv("QINIU_ACCESS_KEY")); +// config.setAccessSecret(System.getenv("QINIU_SECRET_KEY")); + config.setAccessKey("b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8"); + config.setAccessSecret("kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP"); + config.setBucket("ruoyi-vue-pro-private"); + config.setDomain("http://t151glocd.hn-bkt.clouddn.com"); // 如果有自定义域名,则可以设置。http://static.yudao.iocoder.cn + config.setEnablePathStyleAccess(false); + // 默认上海的 endpoint + config.setEndpoint("s3-cn-south-1.qiniucs.com"); + + // 校验配置 + ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config); + // 创建 Client + S3FileClient client = new S3FileClient(0L, config); + client.init(); + // 执行生成 URL 签名 + String path = "output.png"; + String presignedUrl = client.presignGetUrl(path, 300); + System.out.println(presignedUrl); + } + @Test @Disabled // 华为云存储,如果要集成测试,可以注释本行 public void testHuaweiCloud() throws Exception { @@ -94,7 +121,7 @@ public class S3FileClientTest { testExecuteUpload(config); } - private void testExecuteUpload(S3FileClientConfig config) throws Exception { + private void testExecuteUpload(S3FileClientConfig config) { // 校验配置 ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config); // 创建 Client diff --git a/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java b/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java index 0ac9c58da..6af2f65cb 100755 --- a/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java +++ b/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java @@ -30,7 +30,7 @@ public interface CouponConvert { CouponRespDTO convert(CouponDO bean); default CouponDO convert(CouponTemplateDO template, Long userId) { - CouponDO couponDO = new CouponDO() + CouponDO coupon = new CouponDO() .setTemplateId(template.getId()) .setName(template.getName()) .setTakeType(template.getTakeType()) @@ -44,13 +44,13 @@ public interface CouponConvert { .setStatus(CouponStatusEnum.UNUSED.getStatus()) .setUserId(userId); if (CouponTemplateValidityTypeEnum.DATE.getType().equals(template.getValidityType())) { - couponDO.setValidStartTime(template.getValidStartTime()); - couponDO.setValidEndTime(template.getValidEndTime()); + coupon.setValidStartTime(template.getValidStartTime()); + coupon.setValidEndTime(template.getValidEndTime()); } else if (CouponTemplateValidityTypeEnum.TERM.getType().equals(template.getValidityType())) { - couponDO.setValidStartTime(LocalDateTime.now().plusDays(template.getFixedStartTerm())); - couponDO.setValidEndTime(LocalDateTime.now().plusDays(template.getFixedEndTerm())); + coupon.setValidStartTime(LocalDateTime.now().plusDays(template.getFixedStartTerm())); + coupon.setValidEndTime(coupon.getValidStartTime().plusDays(template.getFixedEndTerm())); } - return couponDO; + return coupon; } CouponPageReqVO convert(AppCouponPageReqVO pageReqVO, Collection userIds); diff --git a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeStatusSyncToWxaOrderHandler.java b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeStatusSyncToWxaOrderHandler.java index ec3dc6502..83057a65a 100644 --- a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeStatusSyncToWxaOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeStatusSyncToWxaOrderHandler.java @@ -43,6 +43,40 @@ public class TradeStatusSyncToWxaOrderHandler implements TradeOrderHandler { if (ObjUtil.notEqual(order.getPayChannelCode(), PayChannelEnum.WX_LITE.getCode())) { return; } + + // 上传订单物流信息到微信小程序 + uploadWxaOrderShippingInfo(order); + } + + @Override + public void afterReceiveOrder(TradeOrderDO order) { + // 注意:只有微信小程序支付的订单,才需要同步 + if (ObjUtil.notEqual(order.getPayChannelCode(), PayChannelEnum.WX_LITE.getCode())) { + return; + } + PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId()).getCheckedData(); + SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO = new SocialWxaOrderNotifyConfirmReceiveReqDTO() + .setTransactionId(payOrder.getChannelOrderNo()) + .setReceivedTime(order.getReceiveTime()); + try { + socialClientApi.notifyWxaOrderConfirmReceive(UserTypeEnum.MEMBER.getValue(), reqDTO); + } catch (Exception ex) { + log.error("[afterReceiveOrder][订单({}) 通知订单收货到微信小程序失败]", order, ex); + } + + // 如果是门店自提订单,上传订单物流信息到微信小程序 + // 原因是,门店自提订单没有 “afterDeliveryOrder” 阶段。可见 https://t.zsxq.com/KWD3u 反馈 + if (DeliveryTypeEnum.PICK_UP.getType().equals(order.getDeliveryType())) { + uploadWxaOrderShippingInfo(order); + } + } + + /** + * 上传订单物流信息到微信小程序 + * + * @param order 订单 + */ + private void uploadWxaOrderShippingInfo(TradeOrderDO order) { PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId()).getCheckedData(); SocialWxaOrderUploadShippingInfoReqDTO reqDTO = new SocialWxaOrderUploadShippingInfoReqDTO() .setTransactionId(payOrder.getChannelOrderNo()) @@ -59,29 +93,12 @@ public class TradeStatusSyncToWxaOrderHandler implements TradeOrderHandler { reqDTO.setLogisticsType(SocialWxaOrderUploadShippingInfoReqDTO.LOGISTICS_TYPE_VIRTUAL); } try { - socialClientApi.uploadWxaOrderShippingInfo(UserTypeEnum.MEMBER.getValue(), reqDTO).checkError(); + socialClientApi.uploadWxaOrderShippingInfo(UserTypeEnum.MEMBER.getValue(), reqDTO); } catch (Exception ex) { log.error("[afterDeliveryOrder][订单({}) 上传订单物流信息到微信小程序失败]", order, ex); } } - @Override - public void afterReceiveOrder(TradeOrderDO order) { - // 注意:只有微信小程序支付的订单,才需要同步 - if (ObjUtil.notEqual(order.getPayChannelCode(), PayChannelEnum.WX_LITE.getCode())) { - return; - } - PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId()).getCheckedData(); - SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO = new SocialWxaOrderNotifyConfirmReceiveReqDTO() - .setTransactionId(payOrder.getChannelOrderNo()) - .setReceivedTime(order.getReceiveTime()); - try { - socialClientApi.notifyWxaOrderConfirmReceive(UserTypeEnum.MEMBER.getValue(), reqDTO).getCheckedData(); - } catch (Exception ex) { - log.error("[afterReceiveOrder][订单({}) 通知订单收货到微信小程序失败]", order, ex); - } - } - // TODO @芋艿:【设置路径】 https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order-shipping/order-shipping.html#%E5%85%AD%E3%80%81%E6%B6%88%E6%81%AF%E8%B7%B3%E8%BD%AC%E8%B7%AF%E5%BE%84%E8%AE%BE%E7%BD%AE%E6%8E%A5%E5%8F%A3 } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsLogMapper.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsLogMapper.java index f2388711a..31245fd0d 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsLogMapper.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsLogMapper.java @@ -22,4 +22,8 @@ public interface SmsLogMapper extends BaseMapperX { .orderByDesc(SmsLogDO::getId)); } + default SmsLogDO selectByApiSerialNo(String apiSerialNo) { + return selectOne(SmsLogDO::getApiSerialNo, apiSerialNo); + } + } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java index 19cde8c26..653458f46 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java @@ -119,6 +119,7 @@ public class TencentSmsClient extends AbstractSmsClient { return new SmsReceiveRespDTO() .setSuccess("SUCCESS".equals(statusObj.getStr("report_status"))) // 是否接收成功 .setErrorCode(statusObj.getStr("errmsg")) // 状态报告编码 + .setErrorMsg(statusObj.getStr("description")) // 状态报告描述 .setMobile(statusObj.getStr("mobile")) // 手机号 .setReceiveTime(statusObj.getLocalDateTime("user_receive_time", null)) // 状态报告时间 .setSerialNo(statusObj.getStr("sid")); // 发送序列号 diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsLogService.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsLogService.java index 0c86c0f07..49ec93aac 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsLogService.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsLogService.java @@ -12,7 +12,7 @@ import java.util.Map; * 短信日志 Service 接口 * * @author zzf - * @date 13:48 2021/3/2 + * @since 13:48 2021/3/2 */ public interface SmsLogService { @@ -49,12 +49,13 @@ public interface SmsLogService { * 更新日志的接收结果 * * @param id 日志编号 + * @param apiSerialNo 发送编号 * @param success 是否接收成功 * @param receiveTime 用户接收时间 * @param apiReceiveCode API 接收结果的编码 * @param apiReceiveMsg API 接收结果的说明 */ - void updateSmsReceiveResult(Long id, Boolean success, + void updateSmsReceiveResult(Long id, String apiSerialNo, Boolean success, LocalDateTime receiveTime, String apiReceiveCode, String apiReceiveMsg); /** diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsLogServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsLogServiceImpl.java index c79b94123..45660e60e 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsLogServiceImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsLogServiceImpl.java @@ -10,7 +10,8 @@ import cn.iocoder.yudao.module.system.enums.sms.SmsSendStatusEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import javax.annotation.Resource; +import jakarta.annotation.Resource; + import java.time.LocalDateTime; import java.util.Map; import java.util.Objects; @@ -63,10 +64,17 @@ public class SmsLogServiceImpl implements SmsLogService { } @Override - public void updateSmsReceiveResult(Long id, Boolean success, LocalDateTime receiveTime, + public void updateSmsReceiveResult(Long id, String apiSerialNo, Boolean success, LocalDateTime receiveTime, String apiReceiveCode, String apiReceiveMsg) { SmsReceiveStatusEnum receiveStatus = Objects.equals(success, true) ? SmsReceiveStatusEnum.SUCCESS : SmsReceiveStatusEnum.FAILURE; + if (id == null || id == 0) { + SmsLogDO log = smsLogMapper.selectByApiSerialNo(apiSerialNo); + if (log == null) { + return; + } + id = log.getId(); + } smsLogMapper.updateById(SmsLogDO.builder().id(id).receiveStatus(receiveStatus.getStatus()) .receiveTime(receiveTime).apiReceiveCode(apiReceiveCode).apiReceiveMsg(apiReceiveMsg).build()); } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsSendServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsSendServiceImpl.java index 49af0ecea..89adc3da7 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsSendServiceImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsSendServiceImpl.java @@ -184,7 +184,7 @@ public class SmsSendServiceImpl implements SmsSendService { return; } // 更新短信日志的接收结果. 因为量一般不大,所以先使用 for 循环更新 - receiveResults.forEach(result -> smsLogService.updateSmsReceiveResult(result.getLogId(), + receiveResults.forEach(result -> smsLogService.updateSmsReceiveResult(result.getLogId(), result.getSerialNo(), result.getSuccess(), result.getReceiveTime(), result.getErrorCode(), result.getErrorMsg())); } diff --git a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsLogServiceImplTest.java b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsLogServiceImplTest.java index eed34b988..0664f4f8f 100644 --- a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsLogServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsLogServiceImplTest.java @@ -153,13 +153,14 @@ public class SmsLogServiceImplTest extends BaseDbUnitTest { smsLogMapper.insert(dbSmsLog); // 准备参数 Long id = dbSmsLog.getId(); + String apiSerialNo = dbSmsLog.getApiSerialNo(); Boolean success = randomBoolean(); LocalDateTime receiveTime = randomLocalDateTime(); String apiReceiveCode = randomString(); String apiReceiveMsg = randomString(); // 调用 - smsLogService.updateSmsReceiveResult(id, success, receiveTime, apiReceiveCode, apiReceiveMsg); + smsLogService.updateSmsReceiveResult(id, apiSerialNo, success, receiveTime, apiReceiveCode, apiReceiveMsg); // 断言 dbSmsLog = smsLogMapper.selectById(id); assertEquals(success ? SmsReceiveStatusEnum.SUCCESS.getStatus() diff --git a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsSendServiceImplTest.java b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsSendServiceImplTest.java index 487c6f7fe..ffcc0587d 100644 --- a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsSendServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsSendServiceImplTest.java @@ -291,7 +291,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest { // 调用 smsSendService.receiveSmsStatus(channelCode, text); // 断言 - receiveResults.forEach(result -> smsLogService.updateSmsReceiveResult(eq(result.getLogId()), eq(result.getSuccess()), + receiveResults.forEach(result -> smsLogService.updateSmsReceiveResult(eq(result.getLogId()), eq(result.getSerialNo()), eq(result.getSuccess()), eq(result.getReceiveTime()), eq(result.getErrorCode()), eq(result.getErrorCode()))); }