You've already forked DataMate
init datamate
This commit is contained in:
37
backend/shared/domain-common/pom.xml
Normal file
37
backend/shared/domain-common/pom.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>data-mate-platform</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>domain-common</artifactId>
|
||||
<name>Domain Common</name>
|
||||
<description>DDD领域通用组件</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.datamate.common.domain;
|
||||
|
||||
import com.datamate.common.domain.model.base.BaseEntity;
|
||||
|
||||
/**
|
||||
* DDD聚合根基类
|
||||
*/
|
||||
public abstract class AggregateRoot<ID> extends BaseEntity<ID> {
|
||||
|
||||
protected AggregateRoot() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected AggregateRoot(ID id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聚合版本号(用于乐观锁)
|
||||
*/
|
||||
public abstract Long getVersion();
|
||||
|
||||
/**
|
||||
* 设置聚合版本号
|
||||
*/
|
||||
public abstract void setVersion(Long version);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.datamate.common.domain;
|
||||
|
||||
/**
|
||||
* DDD值对象基类
|
||||
*/
|
||||
public abstract class ValueObject {
|
||||
|
||||
@Override
|
||||
public abstract boolean equals(Object obj);
|
||||
|
||||
@Override
|
||||
public abstract int hashCode();
|
||||
|
||||
@Override
|
||||
public abstract String toString();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.datamate.common.domain.model;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 文件切片上传请求实体(与数据库表 t_chunk_upload_request 对齐)
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class ChunkUploadPreRequest {
|
||||
|
||||
private String id; // UUID
|
||||
private Integer totalFileNum; // 总文件数
|
||||
private Integer uploadedFileNum; // 已上传文件数
|
||||
private String uploadPath; // 文件路径
|
||||
private LocalDateTime timeout; // 上传请求超时时间
|
||||
private String serviceId; // 上传请求所属服务:DATA-MANAGEMENT(数据管理)
|
||||
private String checkInfo; // 业务信息
|
||||
|
||||
/**
|
||||
* 增加已上传文件数
|
||||
*/
|
||||
public void incrementUploadedFileNum() {
|
||||
if (this.uploadedFileNum == null) {
|
||||
this.uploadedFileNum = 1;
|
||||
return;
|
||||
}
|
||||
this.uploadedFileNum++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已完成上传
|
||||
*/
|
||||
public boolean isUploadComplete() {
|
||||
return this.uploadedFileNum != null && this.uploadedFileNum.equals(this.totalFileNum);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已超时
|
||||
*/
|
||||
public boolean isRequestTimeout() {
|
||||
return this.timeout != null && LocalDateTime.now().isAfter(this.timeout);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.datamate.common.domain.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 文件切片上传请求实体(与数据库表 t_chunk_upload_request 对齐)
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class ChunkUploadRequest {
|
||||
/** 预上传返回的id,用来确认同一个任务 */
|
||||
private String reqId;
|
||||
|
||||
/** 文件编号,用于标识批量上传中的第几个文件 */
|
||||
private int fileNo;
|
||||
|
||||
/** 文件名称 */
|
||||
private String fileName;
|
||||
|
||||
/** 文件总分块数量 */
|
||||
private int totalChunkNum;
|
||||
|
||||
/** 当前分块编号,从1开始 */
|
||||
private int chunkNo;
|
||||
|
||||
/** 上传的文件分块内容 */
|
||||
private MultipartFile file;
|
||||
|
||||
/** 切片大小 */
|
||||
private Long fileSize;
|
||||
|
||||
/** 文件分块的校验和(十六进制字符串),用于验证文件完整性 */
|
||||
private String checkSumHex;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.datamate.common.domain.model;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class FileUploadResult {
|
||||
/** 切片是否已经全部上传 */
|
||||
boolean isAllFilesUploaded;
|
||||
|
||||
/** 业务上传信息 */
|
||||
String checkInfo;
|
||||
|
||||
/** 保存的文件 */
|
||||
File savedFile;
|
||||
|
||||
/** 真实文件名 */
|
||||
String fileName;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.datamate.common.domain.model;
|
||||
|
||||
/**
|
||||
* 上传检查信息基类
|
||||
*/
|
||||
public abstract class UploadCheckInfo {
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.datamate.common.domain.model.base;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 实体基类
|
||||
*
|
||||
* @param <ID> 实体ID类型
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public abstract class BaseEntity<ID> implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 实体ID
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
protected ID id;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
protected LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
protected LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
protected String createdBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
protected String updatedBy;
|
||||
|
||||
public BaseEntity(ID id) {
|
||||
super();
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.datamate.common.domain.service;
|
||||
|
||||
import com.datamate.common.domain.model.ChunkUploadPreRequest;
|
||||
import com.datamate.common.domain.model.ChunkUploadRequest;
|
||||
import com.datamate.common.domain.model.FileUploadResult;
|
||||
import com.datamate.common.domain.utils.ChunksSaver;
|
||||
import com.datamate.common.infrastructure.mapper.ChunkUploadRequestMapper;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 文件服务
|
||||
*/
|
||||
@Component
|
||||
public class FileService {
|
||||
private static final int DEFAULT_TIMEOUT = 120;
|
||||
|
||||
private final ChunkUploadRequestMapper chunkUploadRequestMapper;
|
||||
|
||||
public FileService(ChunkUploadRequestMapper chunkUploadRequestMapper) {
|
||||
this.chunkUploadRequestMapper = chunkUploadRequestMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预上传
|
||||
*/
|
||||
@Transactional
|
||||
public String preUpload(ChunkUploadPreRequest chunkUploadPreRequest) {
|
||||
chunkUploadPreRequest.setId(UUID.randomUUID().toString());
|
||||
chunkUploadPreRequest.setTimeout(LocalDateTime.now().plusSeconds(DEFAULT_TIMEOUT));
|
||||
chunkUploadRequestMapper.insert(chunkUploadPreRequest);
|
||||
return chunkUploadPreRequest.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 切片上传
|
||||
*/
|
||||
@Transactional
|
||||
public FileUploadResult chunkUpload(ChunkUploadRequest uploadFileRequest) {
|
||||
uploadFileRequest.setFileSize(uploadFileRequest.getFile().getSize());
|
||||
ChunkUploadPreRequest preRequest = chunkUploadRequestMapper.findById(uploadFileRequest.getReqId());
|
||||
if (preRequest == null || preRequest.isUploadComplete() || preRequest.isRequestTimeout()) {
|
||||
throw new IllegalArgumentException("预上传请求不存在");
|
||||
}
|
||||
File savedFile;
|
||||
if (uploadFileRequest.getTotalChunkNum() > 1) {
|
||||
savedFile = uploadChunk(uploadFileRequest, preRequest);
|
||||
} else {
|
||||
savedFile = uploadFile(uploadFileRequest, preRequest);
|
||||
}
|
||||
if (chunkUploadRequestMapper.update(preRequest) == 0) {
|
||||
throw new IllegalArgumentException("预上传请求不存在");
|
||||
}
|
||||
boolean isFinish = Objects.equals(preRequest.getUploadedFileNum(), preRequest.getTotalFileNum());
|
||||
if (isFinish) {
|
||||
// 删除存分片的临时路径
|
||||
ChunksSaver.deleteFiles(new File(preRequest.getUploadPath(),
|
||||
String.format(ChunksSaver.TEMP_DIR_NAME_FORMAT, preRequest.getId())).getPath());
|
||||
chunkUploadRequestMapper.deleteById(preRequest.getId());
|
||||
}
|
||||
return FileUploadResult.builder()
|
||||
.isAllFilesUploaded(isFinish)
|
||||
.checkInfo(preRequest.getCheckInfo())
|
||||
.savedFile(savedFile)
|
||||
.fileName(uploadFileRequest.getFileName())
|
||||
.build();
|
||||
}
|
||||
|
||||
private File uploadFile(ChunkUploadRequest fileUploadRequest, ChunkUploadPreRequest preRequest) {
|
||||
File savedFile = ChunksSaver.saveFile(fileUploadRequest, preRequest);
|
||||
preRequest.setTimeout(LocalDateTime.now().plusSeconds(DEFAULT_TIMEOUT));
|
||||
preRequest.incrementUploadedFileNum();
|
||||
return savedFile;
|
||||
}
|
||||
|
||||
private File uploadChunk(ChunkUploadRequest fileUploadRequest, ChunkUploadPreRequest preRequest) {
|
||||
Optional<File> savedFile = ChunksSaver.save(fileUploadRequest, preRequest);
|
||||
if (savedFile.isPresent()) {
|
||||
preRequest.incrementUploadedFileNum();
|
||||
return savedFile.get();
|
||||
}
|
||||
preRequest.setTimeout(LocalDateTime.now().plusSeconds(DEFAULT_TIMEOUT));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.datamate.common.domain.utils;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 解析工具类
|
||||
*/
|
||||
public class AnalyzerUtils {
|
||||
/** zip压缩包文件后缀类型 */
|
||||
public static final String TYPE_ZIP = "zip";
|
||||
|
||||
/** tar压缩包文件后缀类型 */
|
||||
public static final String TYPE_TAR_GZ = "tar.gz";
|
||||
|
||||
private static final List<String> SPECIAL_EXTENSIONS = Collections.singletonList(TYPE_TAR_GZ);
|
||||
|
||||
/**
|
||||
* 从文件路径获取文件后缀类型
|
||||
*
|
||||
* @param filePath 文件类型
|
||||
* @return 文件后缀类型
|
||||
*/
|
||||
public static String getExtension(final String filePath) {
|
||||
String filename = CommonUtils.trimFilePath(filePath);
|
||||
for (String ext : SPECIAL_EXTENSIONS) {
|
||||
if (StringUtils.endsWithIgnoreCase(filename, "." + ext)) {
|
||||
return ext;
|
||||
}
|
||||
}
|
||||
int firstDotIndex = filename.lastIndexOf(".");
|
||||
if (firstDotIndex == -1) {
|
||||
return "";
|
||||
}
|
||||
return filename.substring(firstDotIndex + 1).toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.datamate.common.domain.utils;
|
||||
|
||||
import com.datamate.common.domain.model.ChunkUploadPreRequest;
|
||||
import com.datamate.common.domain.model.ChunkUploadRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
public class ChunksSaver {
|
||||
/**
|
||||
* 分片保存的临时目录
|
||||
*/
|
||||
public static final String TEMP_DIR_NAME_FORMAT = "req_%s_chunks";
|
||||
|
||||
/**
|
||||
* 保存分片
|
||||
*
|
||||
* @param fileUploadRequest 上传分片的请求
|
||||
* @param preUploadReq 上传文件的请求
|
||||
* @return 保存后完整的文件
|
||||
*/
|
||||
public static Optional<File> save(ChunkUploadRequest fileUploadRequest, ChunkUploadPreRequest preUploadReq) {
|
||||
Path uploadPath = Paths.get(preUploadReq.getUploadPath(),
|
||||
String.format(TEMP_DIR_NAME_FORMAT, preUploadReq.getId()));
|
||||
LocalDateTime startTime = LocalDateTime.now();
|
||||
// 临时文件名为文件序号
|
||||
File targetFile = new File(uploadPath.toString(), String.valueOf(fileUploadRequest.getFileNo()));
|
||||
|
||||
// 追加分片到目标文件末尾
|
||||
appendToTargetFile(targetFile, getFileInputStream(fileUploadRequest.getFile()));
|
||||
|
||||
// 判断是否分片已经全部上传,全部上传后将重组文件移动到指定路径,否则返回空
|
||||
if (fileUploadRequest.getTotalChunkNum() != fileUploadRequest.getChunkNo()) {
|
||||
log.debug("save chunk {} cost {}", fileUploadRequest.getChunkNo(),
|
||||
ChronoUnit.MILLIS.between(startTime, LocalDateTime.now()));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
File finalFile = new File(preUploadReq.getUploadPath(), fileUploadRequest.getFileName());
|
||||
if (!targetFile.renameTo(finalFile)) {
|
||||
log.error("failed to mv file:{}, req Id:{}", targetFile.getName(), fileUploadRequest.getReqId());
|
||||
throw new IllegalArgumentException("failed to move file to target dir");
|
||||
}
|
||||
log.debug("save chunk {} cost {}", fileUploadRequest.getChunkNo(),
|
||||
ChronoUnit.MILLIS.between(startTime, LocalDateTime.now()));
|
||||
return Optional.of(finalFile);
|
||||
}
|
||||
|
||||
private static InputStream getFileInputStream(MultipartFile file) {
|
||||
try {
|
||||
return file.getInputStream();
|
||||
} catch (IOException e) {
|
||||
log.error("get uploaded file input stream failed", e);
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文件
|
||||
*
|
||||
* @param fileUploadRequest 上传分片的请求
|
||||
* @param preUploadReq 上传文件的请求
|
||||
*/
|
||||
public static File saveFile(ChunkUploadRequest fileUploadRequest, ChunkUploadPreRequest preUploadReq) {
|
||||
// 保存文件
|
||||
File targetFile = new File(preUploadReq.getUploadPath(), fileUploadRequest.getFileName());
|
||||
try {
|
||||
log.info("file path {}, file size {}", targetFile.toPath(), targetFile.getTotalSpace());
|
||||
FileUtils.copyInputStreamToFile(getFileInputStream(fileUploadRequest.getFile()), targetFile);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return targetFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加分片到文件末尾
|
||||
*
|
||||
* @param targetFile 目标文件
|
||||
* @param inputStream file stream
|
||||
*/
|
||||
public static void appendToTargetFile(File targetFile, InputStream inputStream) {
|
||||
try {
|
||||
byte[] buffer = new byte[1024 * 1024];
|
||||
int byteRead;
|
||||
while ((byteRead = inputStream.read(buffer)) != -1) {
|
||||
FileUtils.writeByteArrayToFile(targetFile, buffer, 0, byteRead, true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定路径下的所有文件
|
||||
*
|
||||
* @param uploadPath 文件路径
|
||||
*/
|
||||
public static void deleteFiles(String uploadPath) {
|
||||
File dic = new File(uploadPath);
|
||||
if (!dic.exists()) {
|
||||
return;
|
||||
}
|
||||
File[] files = dic.listFiles();
|
||||
if (files == null || files.length == 0) {
|
||||
dic.delete();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
deleteFiles(file.getPath());
|
||||
} else {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
if (dic.exists()) {
|
||||
dic.delete();
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
log.warn("Fail to delete file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.datamate.common.domain.utils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 通用工具类
|
||||
*/
|
||||
public class CommonUtils {
|
||||
/**
|
||||
* 从文件路径中获取文件名(带后缀)
|
||||
*
|
||||
* @param filePath 文件路径
|
||||
* @return 文件名(带后缀)
|
||||
*/
|
||||
public static String trimFilePath(String filePath) {
|
||||
int lastSlashIndex = filePath.lastIndexOf(File.separator);
|
||||
|
||||
String filename = filePath;
|
||||
if (lastSlashIndex != -1) {
|
||||
filename = filePath.substring(lastSlashIndex + 1);
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.datamate.common.infrastructure.common;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 忽略响应包装注解
|
||||
* <p>
|
||||
* 在使用全局响应包装时,如果某个接口或类不需要进行响应包装,可以使用此注解进行标记
|
||||
* </p>
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface IgnoreResponseWrap {
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.datamate.common.infrastructure.common;
|
||||
|
||||
import com.datamate.common.infrastructure.exception.ErrorCode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 通用返回体
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Response<T> implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 构造成功时的返回体
|
||||
*
|
||||
* @param data 返回数据
|
||||
* @param <T> 返回数据类型
|
||||
* @return 返回体内容
|
||||
*/
|
||||
public static <T> Response<T> ok(T data) {
|
||||
return new Response<>("0", "success", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造错误时的返回体
|
||||
*
|
||||
* @param errorCode 错误码
|
||||
* @param data 返回数据
|
||||
* @param <T> 返回数据类型
|
||||
* @return 返回体内容
|
||||
*/
|
||||
public static <T> Response<T> error(ErrorCode errorCode, T data) {
|
||||
return new Response<>(errorCode.getCode(), errorCode.getMessage(), data);
|
||||
}
|
||||
|
||||
public static <T> Response<T> error(ErrorCode errorCode) {
|
||||
return new Response<>(errorCode.getCode(), errorCode.getMessage(), null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.datamate.common.infrastructure.config;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 持久化实体元数据对象处理器
|
||||
*
|
||||
* @author dallas
|
||||
* @since 2025-10-17
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class EntityMetaObjectHandler implements MetaObjectHandler {
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
log.debug("Starting insert fill...");
|
||||
|
||||
// 创建时间填充
|
||||
this.strictInsertFill(metaObject, "createdAt", LocalDateTime.class, LocalDateTime.now());
|
||||
// 更新时间填充
|
||||
this.strictInsertFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
|
||||
// 创建人填充(需要从安全上下文获取当前用户)
|
||||
String currentUser = getCurrentUser();
|
||||
this.strictInsertFill(metaObject, "createdBy", String.class, currentUser);
|
||||
// 更新人填充
|
||||
this.strictInsertFill(metaObject, "updatedBy", String.class, currentUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
log.debug("Starting update fill...");
|
||||
// 更新时间填充
|
||||
this.strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
|
||||
// 更新人填充
|
||||
this.strictUpdateFill(metaObject, "updatedBy", String.class, getCurrentUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户(需要根据你的安全框架实现)
|
||||
*/
|
||||
private String getCurrentUser() {
|
||||
// todo 这里需要根据你的安全框架实现,例如Spring Security、Shiro等
|
||||
// 示例:返回默认用户或从SecurityContext获取
|
||||
try {
|
||||
// 如果是Spring Security
|
||||
// return SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
|
||||
// 临时返回默认值,请根据实际情况修改
|
||||
return "system";
|
||||
} catch (Exception e) {
|
||||
log.error("Error getting current user", e);
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.datamate.common.infrastructure.config;
|
||||
|
||||
import com.datamate.common.infrastructure.common.Response;
|
||||
import com.datamate.common.infrastructure.exception.BusinessException;
|
||||
import com.datamate.common.infrastructure.exception.SystemErrorCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author dallas
|
||||
* @since 2025-10-17
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
/**
|
||||
* 处理自定义业务异常
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public ResponseEntity<Response<?>> handleBusinessException(BusinessException e) {
|
||||
log.warn("BusinessException: code={}, message={}", e.getCode(), e.getMessage(), e);
|
||||
return ResponseEntity.internalServerError().body(Response.error(e.getErrorCodeEnum()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数校验和数据绑定异常
|
||||
*/
|
||||
@ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class})
|
||||
public ResponseEntity<Response<?>> handleMethodArgumentNotValidException(BindException e) {
|
||||
String message = e.getBindingResult().getFieldErrors().stream()
|
||||
.map(FieldError::getDefaultMessage)
|
||||
.collect(Collectors.joining(", "));
|
||||
log.warn("Parameter validation failed: {}", message);
|
||||
return ResponseEntity.badRequest().body(Response.error(SystemErrorCode.INVALID_PARAMETER, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理系统兜底异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<Response<?>> handleException(Exception e) {
|
||||
log.error("SystemException: ", e);
|
||||
return ResponseEntity.internalServerError().body(Response.error(SystemErrorCode.SYSTEM_BUSY));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.datamate.common.infrastructure.config;
|
||||
|
||||
import com.datamate.common.infrastructure.common.IgnoreResponseWrap;
|
||||
import com.datamate.common.infrastructure.common.Response;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||
|
||||
/**
|
||||
* 全局响应处理器
|
||||
*
|
||||
* @author dallas
|
||||
* @since 2025-10-17
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@RestControllerAdvice(basePackages = "com.datamate")
|
||||
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 判断哪些返回值需要被包装。
|
||||
* 返回true表示执行beforeBodyWrite方法。
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType, @Nullable Class converterType) {
|
||||
// 1. 如果返回类型已经是Response,直接跳过包装
|
||||
if (returnType.getParameterType().isAssignableFrom(Response.class)) {
|
||||
return false;
|
||||
}
|
||||
// 2. 检查方法或所在类上是否有@IgnoreResponseWrap的注解,如果有,返回false
|
||||
if (returnType.hasMethodAnnotation(IgnoreResponseWrap.class) ||
|
||||
returnType.getContainingClass().isAnnotationPresent(IgnoreResponseWrap.class)) {
|
||||
return false;
|
||||
}
|
||||
// 3. 默认情况下,对其他返回类型进行包装
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对响应体进行实际包装处理。
|
||||
*/
|
||||
@Override
|
||||
public Object beforeBodyWrite(Object body,
|
||||
@Nullable MethodParameter returnType,
|
||||
@Nullable MediaType selectedContentType,
|
||||
@Nullable Class selectedConverterType,
|
||||
@Nullable ServerHttpRequest request,
|
||||
@Nullable ServerHttpResponse response) {
|
||||
// 如果返回体本身就是Response类型(通常来自异常处理),直接返回
|
||||
if (body instanceof Response) {
|
||||
return body;
|
||||
}
|
||||
|
||||
// 如果返回值是String类型,需要特殊处理
|
||||
if (body instanceof String) {
|
||||
// 手动将Response对象序列化为JSON字符串返回
|
||||
try {
|
||||
return objectMapper.writeValueAsString(Response.ok(body));
|
||||
} catch (JsonProcessingException e) {
|
||||
// 记录日志或抛出运行时异常
|
||||
log.error("Error serializing response", e);
|
||||
throw new RuntimeException("Error converting response to JSON", e);
|
||||
}
|
||||
}
|
||||
// 对于正常的返回结果,统一包装成成功的Response
|
||||
return Response.ok(body);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.datamate.common.infrastructure.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
/**
|
||||
* Mybatis Plus 配置类
|
||||
*
|
||||
* @author dallas
|
||||
* @since 2025-10-20
|
||||
*/
|
||||
@Configuration
|
||||
public class MybatisPlusConfig {
|
||||
/**
|
||||
* 配置 JacksonTypeHandler 以支持 Java 8 日期时间类型
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public JacksonTypeHandler jacksonTypeHandler() {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
JacksonTypeHandler handler = new JacksonTypeHandler(Object.class);
|
||||
JacksonTypeHandler.setObjectMapper(objectMapper);
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置 Mybatis Plus 分页插件
|
||||
*
|
||||
* @return MybatisPlusInterceptor
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.datamate.common.infrastructure.exception;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 断言工具类,用于在业务逻辑中快速检查条件是否满足,不满足时抛出业务异常
|
||||
*
|
||||
* @author dallas
|
||||
* @since 2025-10-17
|
||||
*/
|
||||
public class BusinessAssert {
|
||||
public static void isTrue(boolean condition, ErrorCode errorCode) {
|
||||
if (!condition) {
|
||||
throw BusinessException.of(errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
public static void notNull(Object obj, ErrorCode errorCode) {
|
||||
if (obj == null) {
|
||||
throw BusinessException.of(errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
public static void notEmpty(Collection<?> collection, ErrorCode errorCode) {
|
||||
if (collection == null || collection.isEmpty()) {
|
||||
throw BusinessException.of(errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
public static void isTrue(boolean condition, ErrorCode errorCode, String customMessage) {
|
||||
if (!condition) {
|
||||
throw BusinessException.of(errorCode, customMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.datamate.common.infrastructure.exception;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 业务异常基类
|
||||
*
|
||||
* @author dallas
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
public class BusinessException extends RuntimeException {
|
||||
private final ErrorCode errorCode;
|
||||
private final Map<String, Object> details;
|
||||
|
||||
// 核心构造方法
|
||||
private BusinessException(ErrorCode errorCode, String customMessage,
|
||||
Map<String, Object> details, Throwable cause) {
|
||||
super(customMessage != null ? customMessage : errorCode.getMessage(), cause);
|
||||
this.errorCode = errorCode;
|
||||
this.details = details != null ? new HashMap<>(details) : new HashMap<>();
|
||||
}
|
||||
|
||||
// 静态工厂方法
|
||||
public static BusinessException of(ErrorCode errorCode) {
|
||||
return new BusinessException(errorCode, null, null, null);
|
||||
}
|
||||
|
||||
public static BusinessException of(ErrorCode errorCode, String customMessage) {
|
||||
return new BusinessException(errorCode, customMessage, null, null);
|
||||
}
|
||||
|
||||
public static BusinessException of(ErrorCode errorCode, Map<String, Object> details) {
|
||||
return new BusinessException(errorCode, null, details, null);
|
||||
}
|
||||
|
||||
public static BusinessException of(ErrorCode errorCode, String customMessage,
|
||||
Map<String, Object> details) {
|
||||
return new BusinessException(errorCode, customMessage, details, null);
|
||||
}
|
||||
|
||||
// 快速创建方法 - 支持链式调用添加详情
|
||||
public static BusinessException create(ErrorCode errorCode) {
|
||||
return new BusinessException(errorCode, null, null, null);
|
||||
}
|
||||
|
||||
public BusinessException withDetail(String key, Object value) {
|
||||
this.details.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BusinessException withCustomMessage(String customMessage) {
|
||||
return new BusinessException(this.errorCode, customMessage, this.details, null);
|
||||
}
|
||||
|
||||
// Getter方法
|
||||
public String getCode() {
|
||||
return errorCode.getCode();
|
||||
}
|
||||
|
||||
public ErrorCode getErrorCodeEnum() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public Map<String, Object> getDetails() {
|
||||
return Collections.unmodifiableMap(details);
|
||||
}
|
||||
|
||||
public String getOriginalMessage() {
|
||||
return errorCode.getMessage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.datamate.common.infrastructure.exception;
|
||||
|
||||
/**
|
||||
* 错误码接口
|
||||
*
|
||||
* @author dallas
|
||||
* @since 2025-10-17
|
||||
*/
|
||||
public interface ErrorCode {
|
||||
/**
|
||||
* 获取错误码
|
||||
*
|
||||
* @return 错误码
|
||||
*/
|
||||
String getCode();
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
*
|
||||
* @return 错误信息
|
||||
*/
|
||||
String getMessage();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.datamate.common.infrastructure.exception;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class ErrorCodeImpl implements ErrorCode {
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
||||
public static ErrorCodeImpl of(String code, String message) {
|
||||
return new ErrorCodeImpl(code, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.datamate.common.infrastructure.exception;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 系统错误码枚举
|
||||
*
|
||||
* @author dallas
|
||||
* @since 2025-10-17
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum SystemErrorCode implements ErrorCode {
|
||||
/**
|
||||
* 未知错误
|
||||
*/
|
||||
UNKNOWN_ERROR("sys.0001", "未知错误"),
|
||||
/**
|
||||
* 系统繁忙,请稍后重试
|
||||
*/
|
||||
SYSTEM_BUSY("sys.0002", "系统繁忙,请稍后重试"),
|
||||
/**
|
||||
* 参数错误
|
||||
*/
|
||||
INVALID_PARAMETER("sys.0003", "参数错误"),
|
||||
/**
|
||||
* 资源未找到
|
||||
*/
|
||||
RESOURCE_NOT_FOUND("sys.0004", "资源未找到"),
|
||||
/**
|
||||
* 权限不足
|
||||
*/
|
||||
INSUFFICIENT_PERMISSIONS("sys.0005", "权限不足"),
|
||||
|
||||
/**
|
||||
* 文件系统错误
|
||||
*/
|
||||
FILE_SYSTEM_ERROR("sys.0006", "文件系统错误");
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.datamate.common.infrastructure.mapper;
|
||||
|
||||
import com.datamate.common.domain.model.ChunkUploadPreRequest;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件切片上传请求Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ChunkUploadRequestMapper {
|
||||
|
||||
/**
|
||||
* 根据ID查询
|
||||
*/
|
||||
ChunkUploadPreRequest findById(@Param("id") String id);
|
||||
|
||||
/**
|
||||
* 根据服务ID查询
|
||||
*/
|
||||
List<ChunkUploadPreRequest> findByServiceId(@Param("serviceId") String serviceId);
|
||||
|
||||
/**
|
||||
* 查询所有
|
||||
*/
|
||||
List<ChunkUploadPreRequest> findAll();
|
||||
|
||||
/**
|
||||
* 插入
|
||||
*/
|
||||
int insert(ChunkUploadPreRequest request);
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*/
|
||||
int update(ChunkUploadPreRequest request);
|
||||
|
||||
/**
|
||||
* 根据ID删除
|
||||
*/
|
||||
int deleteById(@Param("id") String id);
|
||||
|
||||
/**
|
||||
* 根据服务ID删除
|
||||
*/
|
||||
int deleteByServiceId(@Param("serviceId") String serviceId);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.datamate.common.interfaces;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PagedResponse <T> {
|
||||
private long page;
|
||||
private long size;
|
||||
private long totalElements;
|
||||
private long totalPages;
|
||||
private List<T> content;
|
||||
|
||||
public PagedResponse(List<T> content) {
|
||||
this.page = 0;
|
||||
this.size = content.size();
|
||||
this.totalElements = content.size();
|
||||
this.totalPages = 1;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public PagedResponse(List<T> content, long page, long totalElements, long totalPages) {
|
||||
this.page = page;
|
||||
this.size = content.size();
|
||||
this.totalElements = totalElements;
|
||||
this.totalPages = totalPages;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public static <T> PagedResponse<T> of(List<T> content) {
|
||||
return new PagedResponse<>(content);
|
||||
}
|
||||
|
||||
public static <T> PagedResponse<T> of(List<T> content, long page, long totalElements, long totalPages) {
|
||||
return new PagedResponse<>(content, page, totalElements, totalPages);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.datamate.common.interfaces;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PagingQuery {
|
||||
/**
|
||||
* 页码,从0开始
|
||||
*/
|
||||
private Integer page = 0;
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
private Integer size = 20;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.datamate.common.infrastructure.mapper.ChunkUploadRequestMapper">
|
||||
|
||||
<resultMap id="ChunkUploadRequestResultMap" type="com.datamate.common.domain.model.ChunkUploadPreRequest">
|
||||
<id column="id" property="id"/>
|
||||
<result column="total_file_num" property="totalFileNum"/>
|
||||
<result column="uploaded_file_num" property="uploadedFileNum"/>
|
||||
<result column="upload_path" property="uploadPath"/>
|
||||
<result column="timeout" property="timeout"/>
|
||||
<result column="service_id" property="serviceId"/>
|
||||
<result column="check_info" property="checkInfo"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
id, total_file_num, uploaded_file_num, upload_path, timeout, service_id, check_info
|
||||
</sql>
|
||||
|
||||
<select id="findById" parameterType="string" resultMap="ChunkUploadRequestResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM t_chunk_upload_request
|
||||
WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="findByServiceId" parameterType="string" resultMap="ChunkUploadRequestResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM t_chunk_upload_request
|
||||
WHERE service_id = #{serviceId}
|
||||
ORDER BY timeout DESC
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultMap="ChunkUploadRequestResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM t_chunk_upload_request
|
||||
ORDER BY timeout DESC
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="com.datamate.common.domain.model.ChunkUploadPreRequest">
|
||||
INSERT INTO t_chunk_upload_request (
|
||||
id, total_file_num, uploaded_file_num, upload_path, timeout, service_id, check_info
|
||||
) VALUES (
|
||||
#{id}, #{totalFileNum}, #{uploadedFileNum}, #{uploadPath}, #{timeout}, #{serviceId}, #{checkInfo}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update" parameterType="com.datamate.common.domain.model.ChunkUploadPreRequest">
|
||||
UPDATE t_chunk_upload_request
|
||||
SET total_file_num = #{totalFileNum},
|
||||
uploaded_file_num = #{uploadedFileNum},
|
||||
upload_path = #{uploadPath},
|
||||
timeout = #{timeout},
|
||||
service_id = #{serviceId},
|
||||
check_info = #{checkInfo}
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById" parameterType="string">
|
||||
DELETE FROM t_chunk_upload_request WHERE id = #{id}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByServiceId" parameterType="string">
|
||||
DELETE FROM t_chunk_upload_request WHERE service_id = #{serviceId}
|
||||
</delete>
|
||||
</mapper>
|
||||
46
backend/shared/security-common/pom.xml
Normal file
46
backend/shared/security-common/pom.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>data-mate-platform</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>security-common</artifactId>
|
||||
<name>Security Common</name>
|
||||
<description>安全通用组件</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.11.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.datamate.common.security;
|
||||
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JWT工具类
|
||||
*/
|
||||
@Component
|
||||
public class JwtUtils {
|
||||
|
||||
@Value("${jwt.secret:datamate-secret-key-for-jwt-token-generation}")
|
||||
private String secret;
|
||||
|
||||
@Value("${jwt.expiration:86400}") // 24小时
|
||||
private Long expiration;
|
||||
|
||||
private SecretKey getSigningKey() {
|
||||
return Keys.hmacShaKeyFor(secret.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成JWT令牌
|
||||
*/
|
||||
public String generateToken(String username, Map<String, Object> claims) {
|
||||
Map<String, Object> tokenClaims = new HashMap<>();
|
||||
if (claims != null) {
|
||||
tokenClaims.putAll(claims);
|
||||
}
|
||||
tokenClaims.put("sub", username);
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(tokenClaims)
|
||||
.setSubject(username)
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
|
||||
.signWith(getSigningKey(), SignatureAlgorithm.HS512)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取用户名
|
||||
*/
|
||||
public String getUsernameFromToken(String token) {
|
||||
return getClaimsFromToken(token).getSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取过期时间
|
||||
*/
|
||||
public Date getExpirationDateFromToken(String token) {
|
||||
return getClaimsFromToken(token).getExpiration();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取声明
|
||||
*/
|
||||
public Claims getClaimsFromToken(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(getSigningKey())
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证令牌是否过期
|
||||
*/
|
||||
public Boolean isTokenExpired(String token) {
|
||||
Date expiration = getExpirationDateFromToken(token);
|
||||
return expiration.before(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证令牌
|
||||
*/
|
||||
public Boolean validateToken(String token, String username) {
|
||||
try {
|
||||
String tokenUsername = getUsernameFromToken(token);
|
||||
return (username.equals(tokenUsername) && !isTokenExpired(token));
|
||||
} catch (JwtException | IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
*/
|
||||
public String refreshToken(String token) {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
claims.setIssuedAt(new Date());
|
||||
claims.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000));
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.signWith(getSigningKey(), SignatureAlgorithm.HS512)
|
||||
.compact();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user