提交 8d6c0a83 作者: 方治民

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

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