提交 8d6c0a83 作者: 方治民

feat: 依赖更新、优化 MinIO 配置并新增文件下载实现、优化 FailStatusException 异常类增加 message 异常消息、请求日志新增状态码输出

上级 e88d1cff
......@@ -95,7 +95,7 @@ public class GlobalExceptionHandler {
*/
@ExceptionHandler(value = FailStatusException.class)
public Result<String> failStatusExceptionHandler(FailStatusException e) {
return Result.no(e.getStatus());
return Result.no(e.getStatus(), e.getMessage());
}
/**
......
......@@ -13,11 +13,11 @@ import com.yiring.common.util.FileUtils;
import com.yiring.common.vo.PageVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
......@@ -64,9 +64,10 @@ public class ExampleController {
return Result.ok(vo);
}
@SneakyThrows
@ApiOperation(value = "download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
public void download(HttpServletResponse response) {
ClassPathResource resource = new ClassPathResource("static/cat.jpg");
FileUtils.download(response, resource.getFile());
}
......
......@@ -43,7 +43,7 @@ minio:
secret-key: minioadmin
end-point: "http://${env.host}:18100"
bucket: basic
domain: ${minio.endpoint}/${minio.bucket}
domain: ${minio.endpoint}
logging:
level:
......
......@@ -44,7 +44,7 @@ minio:
secret-key: minioadmin
end-point: "http://${env.host}:18100"
bucket: basic
domain: ${minio.endpoint}/${minio.bucket}
domain: ${minio.endpoint}
logging:
level:
......
......@@ -44,7 +44,7 @@ minio:
secret-key: minioadmin
end-point: "http://${env.host}:18100"
bucket: basic
domain: ${minio.endpoint}/${minio.bucket}
domain: ${minio.endpoint}
logging:
level:
......
......@@ -44,7 +44,7 @@ minio:
secret-key: minioadmin
end-point: "http://${env.host}:18100"
bucket: basic
domain: ${minio.endpoint}/${minio.bucket}
domain: ${minio.endpoint}
logging:
level:
......
......@@ -48,10 +48,11 @@ public class RequestAspect {
Object result = point.proceed();
long end = System.currentTimeMillis();
// 计算接口处理消耗时间,格式化
String timestamp = LocalDateTime.now().format(DateFormatter.DATE_TIME);
String times = String.format("%.3fs", (double) (end - start) / 1000);
// Print Request Log (Optional Replace: MDC)
// 获取接口请求扩展信息,Header, Params
String extra = "";
if (Boolean.TRUE.equals(debug)) {
String headers = JSONObject.toJSONString(ServletUtil.getHeaderMap(request), SerializerFeature.PrettyFormat);
......@@ -67,20 +68,26 @@ public class RequestAspect {
);
}
}
// 获取接口处理返回的状态码,设置接口响应时间和耗时信息
int status = 200;
if (result instanceof Result) {
((Result<?>) result).setTimestamp(timestamp);
((Result<?>) result).setTimes(times);
status = ((Result<?>) result).getStatus();
}
// 打印请求日志 (Optional Replace: MDC, Trace)
log.info(
"[Request] Method: {}, URL: {}, IP: {}, Times: {}{}",
"[Request] Method: {}, URL: {}, Status: {}, IP: {}, Times: {}{}",
request.getMethod(),
request.getRequestURL(),
status,
Commons.getClientIpAddress(request),
times,
extra
);
if (result instanceof Result) {
((Result<?>) result).setTimestamp(timestamp);
((Result<?>) result).setTimes(times);
}
return result;
}
......
......@@ -6,6 +6,7 @@ import org.springframework.lang.Nullable;
/**
* API 响应状态码
* 包含系统和业务两个维度
* @author ifzm
*/
@SuppressWarnings({ "unused" })
......
......@@ -30,4 +30,9 @@ public class FailStatusException extends RuntimeException {
* 状态
*/
Status status;
/**
* 异常消息
*/
String message;
}
......@@ -59,9 +59,9 @@ public class SwaggerConfig implements CommandLineRunner {
@Resource
OpenApiExtensionResolver openApiExtensionResolver;
@Bean(name = "api.default")
@Bean(name = "api.all")
public Docket api() {
return api("@default", List.of(""), PathSelectors.any());
return api("@All", List.of(""), PathSelectors.any());
}
@Bean(name = "api.auth")
......
dependencies {
implementation project(":basic-common:core")
implementation project(":basic-common:util")
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// swagger(knife4j)
implementation "com.github.xiaoymin:knife4j-spring-boot-starter:${knife4jVersion}"
......
......@@ -28,6 +28,10 @@ public class MinioConfig {
String secretKey;
String bucket;
String domain;
/**
* 外部地址
*/
String externalPoint;
@Bean
public MinioClient getClient() {
......
......@@ -34,8 +34,8 @@ public record Minio(MinioConfig config, MinioClient client) {
* @param object 文件相对地址(含路径)
* @return URI
*/
public String getURI(String object) {
return String.join("/", config.getDomain(), object);
public String getURI(String object, String bucket) {
return String.join("/", config.getDomain(), bucket, object);
}
/**
......@@ -97,8 +97,8 @@ public record Minio(MinioConfig config, MinioClient client) {
* @param object 文件相对地址
* @throws Exception 异常
*/
public void remove(String object) throws Exception {
RemoveObjectArgs args = RemoveObjectArgs.builder().bucket(config.getBucket()).object(object).build();
public void remove(String bucket, String object) throws Exception {
RemoveObjectArgs args = RemoveObjectArgs.builder().bucket(bucket).object(object).build();
client.removeObject(args);
}
......@@ -109,8 +109,8 @@ public record Minio(MinioConfig config, MinioClient client) {
* @return 文件流
* @throws Exception 异常
*/
public GetObjectResponse getObject(String object) throws Exception {
GetObjectArgs args = GetObjectArgs.builder().bucket(config.getBucket()).object(object).build();
public GetObjectResponse getObject(String bucket, String object) throws Exception {
GetObjectArgs args = GetObjectArgs.builder().bucket(bucket).object(object).build();
return client.getObject(args);
}
......@@ -121,8 +121,8 @@ public record Minio(MinioConfig config, MinioClient client) {
* @return 文件信息
* @throws Exception 异常
*/
public StatObjectResponse objectStat(String object) throws Exception {
StatObjectArgs args = StatObjectArgs.builder().bucket(config.getBucket()).object(object).build();
public StatObjectResponse objectStat(String bucket, String object) throws Exception {
StatObjectArgs args = StatObjectArgs.builder().bucket(bucket).object(object).build();
return client.statObject(args);
}
......@@ -132,8 +132,8 @@ public record Minio(MinioConfig config, MinioClient client) {
* @param prefix 文件路径前缀
* @return 文件/文件夹集合
*/
public Iterable<Result<Item>> listObjects(String prefix) {
ListObjectsArgs args = ListObjectsArgs.builder().bucket(config.getBucket()).prefix(prefix).build();
public Iterable<Result<Item>> listObjects(String bucket, String prefix) {
ListObjectsArgs args = ListObjectsArgs.builder().bucket(bucket).prefix(prefix).build();
return client.listObjects(args);
}
......@@ -144,15 +144,10 @@ public record Minio(MinioConfig config, MinioClient client) {
* @param target 目标文件 object
* @throws Exception 异常
*/
public void copyObject(String source, String target) throws Exception {
CopySource copySource = CopySource.builder().bucket(config.getBucket()).object(source).build();
public void copyObject(String bucket, String source, String target) throws Exception {
CopySource copySource = CopySource.builder().bucket(bucket).object(source).build();
CopyObjectArgs args = CopyObjectArgs
.builder()
.bucket(config.getBucket())
.object(target)
.source(copySource)
.build();
CopyObjectArgs args = CopyObjectArgs.builder().bucket(bucket).object(target).source(copySource).build();
client.copyObject(args);
}
......
/* (C) 2022 YiRing, Inc. */
package com.yiring.common.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serial;
import java.io.Serializable;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* MinIO 文件下载请求参数
*
* @author Jim
* @version 0.1
* 2022/7/5 15:29
*/
@ApiModel(value = "DownloadParam", description = "文件下载请求参数")
@Valid
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class DownloadParam implements Serializable {
@Serial
private static final long serialVersionUID = -8690942241103456899L;
@ApiModelProperty(value = "bucket", example = "business", required = true)
@NotEmpty(message = "存储桶不能为空")
String bucket;
@ApiModelProperty(value = "object", example = "cat.jpg", required = true)
@NotEmpty(message = "文件对象不能为空")
String object;
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.common.web;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.yiring.common.config.MinioConfig;
import com.yiring.common.core.Minio;
import com.yiring.common.core.Result;
import com.yiring.common.core.Status;
import com.yiring.common.exception.FailStatusException;
import com.yiring.common.param.DownloadParam;
import com.yiring.common.util.FileUtils;
import io.minio.GetObjectResponse;
import io.minio.ObjectWriteResponse;
import io.minio.StatObjectResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
......@@ -40,6 +47,7 @@ import org.springframework.web.multipart.MultipartFile;
public class MinioController {
final Minio minio;
final MinioConfig minioConfig;
/**
* minio 上传文件,成功返回文件 url
......@@ -55,11 +63,36 @@ public class MinioController {
String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/M/d"));
String folder = "upload/" + date + "/" + uuid;
ObjectWriteResponse response = minio.putObject(file, folder);
String uri = minio.getURI(response.object());
String uri = minio.getURI(response.object(), minioConfig.getBucket());
return Result.ok(uri);
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.no(Status.BAD_REQUEST, "上传失败");
}
}
/**
* MinIO 文件下载(非公开桶)
* @param response HttpServletResponse
* @param param 请求参数
*/
@ApiOperation(value = "文件下载", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@GetMapping("download")
public void download(HttpServletResponse response, @Valid DownloadParam param) {
try {
StatObjectResponse statObject = minio.objectStat(param.getBucket(), param.getObject());
GetObjectResponse object = minio.getObject(param.getBucket(), param.getObject());
long lastModified = Timestamp.from(statObject.lastModified().toInstant()).getTime();
FileUtils.download(
response,
object,
statObject.size(),
FileUtil.getName(statObject.object()),
statObject.contentType(),
lastModified
);
} catch (Exception e) {
throw new FailStatusException(Status.BAD_REQUEST, e.getMessage());
}
}
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.common.util;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import javax.servlet.http.HttpServletResponse;
import lombok.experimental.UtilityClass;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
/**
* 文件工具类
......@@ -30,9 +34,39 @@ public class FileUtils {
*/
public void download(HttpServletResponse response, File file) throws IOException {
String filename = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8);
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(file.length()));
BasicFileAttributes basicFileAttributes = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
long lastModified = basicFileAttributes.lastModifiedTime().toMillis();
response.setHeader(HttpHeaders.LAST_MODIFIED, String.valueOf(lastModified));
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(basicFileAttributes.size()));
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + filename);
response.setContentType(FileUtil.getMimeType(file.toPath()));
FileReader.create(file).writeToStream(response.getOutputStream(), true);
}
/**
* 文件下载
* @param response HttpServletResponse
* @param object 文件流
* @param length 文件大小
* @param filename 文件名称(带后缀)
* @param contentType 文档类型
* @param lastModified 最后修改时间
* @throws IOException IOException
*/
public void download(
HttpServletResponse response,
InputStream object,
long length,
String filename,
String contentType,
long lastModified
) throws IOException {
filename = URLEncoder.encode(filename, StandardCharsets.UTF_8);
response.setHeader(HttpHeaders.LAST_MODIFIED, String.valueOf(lastModified));
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(length));
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + filename);
response.setContentType(contentType);
IOUtils.copy(object, response.getOutputStream());
object.close();
}
}
plugins {
id 'java'
// https://start.spring.io
id 'org.springframework.boot' version '2.6.8'
id 'org.springframework.boot' version '2.6.9'
// https://plugins.gradle.org/plugin/io.spring.dependency-management
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
// https://plugins.gradle.org/plugin/com.diffplug.spotless
......@@ -27,9 +27,9 @@ ext {
// https://mvnrepository.com/artifact/cn.dev33/sa-token-spring-boot-starter
saTokenVersion = '1.30.0'
// https://mvnrepository.com/artifact/cn.hutool/hutool-all
hutoolVersion = '5.8.4.M1'
hutoolVersion = '5.8.4'
// https://mvnrepository.com/artifact/com.alibaba/fastjson
fastJsonVersion = '2.0.7'
fastJsonVersion = '2.0.8'
// https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core
xxlJobVersion = '2.3.1'
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
......@@ -37,11 +37,11 @@ ext {
// https://mvnrepository.com/artifact/io.minio/minio
minioVersion = '8.4.2'
// https://mvnrepository.com/artifact/com.vladmihalcea/hibernate-types-55
hibernateTypesVersion = '2.16.2'
hibernateTypesVersion = '2.16.3'
// https://mvnrepository.com/artifact/org.hibernate/hibernate-spatial
hibernateSpatialVersion = '5.6.9.Final'
// https://mvnrepository.com/artifact/org.locationtech.jts/jts-core
jtsVersion = '1.18.2'
jtsVersion = '1.19.0'
// https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter
mybatisPlusVersion = '3.5.2'
// https://mvnrepository.com/artifact/com.github.liaochong/myexcel
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论