feat:添加APP版本管理相关代码

This commit is contained in:
aguanleishen
2026-07-28 08:32:02 +08:00
parent 0970af239d
commit b5246d4e58
112 changed files with 7504 additions and 25 deletions

View File

@@ -29,6 +29,7 @@ package org.springblade.auth;
import org.springblade.core.cloud.client.BladeCloudApplication; import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication; import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant; import org.springblade.core.launch.constant.AppConstant;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
/** /**
@@ -38,6 +39,7 @@ import org.springframework.session.data.redis.config.annotation.web.http.EnableR
*/ */
@EnableRedisHttpSession @EnableRedisHttpSession
@BladeCloudApplication @BladeCloudApplication
@ComponentScan({"org.springblade.auth", "org.springblade.**.feign"})
public class AuthApplication { public class AuthApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@@ -27,6 +27,18 @@
<artifactId>blade-core-auto</artifactId> <artifactId>blade-core-auto</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.huaweicloud</groupId>
<artifactId>esdk-obs-java-bundle</artifactId>
<version>3.24.9</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -0,0 +1,277 @@
package org.springblade.common.utils;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.*;
import jakarta.activation.MimetypesFileTypeMap;
import lombok.SneakyThrows;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.*;
import java.net.URL;
import java.util.List;
/**
* @ClassName: OBSUtil
* @Description:
* @Author: zhaowei
* @Date: 2024/8/22
*/
@Component
public class ObsUtil {
private final static Logger logger = LoggerFactory.getLogger(ObsUtil.class);
private static MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap();
/**
* 华为云OBS
*/
public static String obsEndpoint;
public static String obsAccessKeyId;
public static String obsAccessKeySecret;
public static String obsBucketName;
public static String obsRootDirectory;
@Value("${obs.endpoint}")
public void setObsEndpoint(String endpoint) {
ObsUtil.obsEndpoint = endpoint;
}
@Value("${obs.access-key-id}")
public void setObsAccessKeyId(String accessKeyId) {
ObsUtil.obsAccessKeyId = accessKeyId;
}
@Value("${obs.access-key-secret}")
public void setObsAccessKeySecret(String accessKeySecret) {
ObsUtil.obsAccessKeySecret = accessKeySecret;
}
@Value("${obs.bucket-name}")
public void setObsBucketName(String bucketName) {
ObsUtil.obsBucketName = bucketName;
}
@Value("${obs.root-directory}")
public void setObsRootDirectory(String rootDirectory) {
ObsUtil.obsRootDirectory = rootDirectory;
}
// 如果Bucket不存在则创建它。
private static void ensureBucket(ObsClient client, String bucketName) throws ObsException {
if (client.headBucket(bucketName)) {
return;
}
// 创建bucket
client.createBucket(bucketName);
}
// 把Bucket设置为所有人可读
private static void setBucketPublicReadable(String bucketName) throws ObsException {
//华为云OBS
ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint);
if (obsClient.doesObjectExist(obsBucketName, obsRootDirectory)) {
} else {
obsClient.createBucket(obsBucketName);
}
obsClient.setBucketAcl(obsBucketName, com.obs.services.model.AccessControlList.REST_CANNED_PUBLIC_READ_WRITE);
}
/**
* 上传一个Object
*
* @param key
* @param content
* @throws FileNotFoundException
*/
public static String putObject(String key, InputStream content) throws IOException {
//华为云OBS
ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint);
try {
obsClient.putObject(obsBucketName, key, content);
} catch (Exception e) {
logger.error("obs put object error", e);
throw new RuntimeException("obs eror" + e.getMessage());
} finally {
obsClient.close();
}
return getExpireUrl(key);
}
/**
* 下载文件
* @param objectKey 上传到OBS起的名
* @param filename 文件下载到本地保存的路径
* @throws ObsException
* @throws IOException
*/
public static void downloadFile(String objectKey, String filename) throws ObsException, IOException {
//华为云OBS
ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint);
try {
obsClient.getObject(obsBucketName, objectKey);
} finally {
obsClient.close();
}
}
/**
* 删除一个Bucket和其中的Objects
*
* @param bucketName Bucket名
* @throws ObsException
*/
private static void deleteBucket(String bucketName) throws ObsException {
ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint);
com.obs.services.model.ObjectListing objectListing = obsClient.listObjects(bucketName);
List<ObsObject> listObject = objectListing.getObjects();
for (ObsObject o : listObject) {
obsClient.deleteObject(bucketName, o.getObjectKey());
}
obsClient.deleteBucket(bucketName);
}
/**
* 批量删除Object
* @param objList Object的key(目录路径/文件名)
*/
public static void delelteObjectList(List<String> objList) {
ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint);
for (String key : objList) {
obsClient.deleteObject(obsBucketName, key);
}
}
/**
* 删除Object
* @param key 目录路径/文件名
*/
public static void delelteObject(String key) {
ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint);
obsClient.deleteObject(obsBucketName, key);
}
/**
* 通过key获取带签名的url
*/
public static String getExpireUrl(String key) {
String fileUrl = "";
ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint);
// URL有效期3600秒.5分钟
// long expireSeconds = 3600L;
// 约等于1年
long expireSeconds = 31536000L;
TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expireSeconds);
request.setBucketName(obsBucketName);
request.setObjectKey(key);
TemporarySignatureResponse response = obsClient.createTemporarySignature(request);
fileUrl = response.getSignedUrl();
return fileUrl;
}
/**
* 上传文件至OSS并且返回链接地址
* @param url 远程路径
* @param ossDir OSS目录
* @param fileName 文件名
* @return 文件OSS链接
* @modify
*/
public static String uploadFile2Oss(URL url, String ossDir, String fileName) {
InputStream is;
String key = ossDir + fileName;
try {
is = url.openStream();
return uploadFile2OSS(is, key);
} catch (Exception ex) {
logger.error("上传失败!", ex);
}
return StringUtils.EMPTY;
}
/**
* 上传到OSS服务器 如果同名文件会覆盖服务器上的
*
* @param inputStream 文件流
* @return 返回文件访问路径
*/
@SneakyThrows
public static String uploadFile2OSS(InputStream inputStream, String key) {
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setCacheControl("no-cache");
String fileName = FilenameUtils.getName(key);
objectMetadata.setContentType(fileTypeMap.getContentType(fileName));
objectMetadata.addUserMetadata("filename", fileName);
ObsClient obsClient = new ObsClient(obsAccessKeyId,
obsAccessKeySecret, obsEndpoint);
try {
obsClient.putObject(obsBucketName, obsRootDirectory + key, inputStream, objectMetadata);
return obsRootDirectory + "/" + key;
} finally {
obsClient.close();
}
}
@SneakyThrows
public Boolean checkObjectExist(String key) {
Boolean existsFlag = false;
ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint);
try {
existsFlag = obsClient.doesObjectExist(obsBucketName, key);
} finally {
obsClient.close();
}
return existsFlag;
}
/**
* 下载文件
* @param objectKey 上传到OBS起的名
* @param filename 文件下载到本地保存的路径
* @throws ObsException
* @throws IOException
*/
public static void downloadFileNew(String objectKey, String filename){
//华为云OBS
ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint);
try {
ObsObject ossObject = obsClient.getObject(obsBucketName, objectKey);
OutputStream outputStream = null;
try {
outputStream = new BufferedOutputStream(new FileOutputStream(filename));
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = ossObject.getObjectContent().read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
} catch (IOException ex) {
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
}
}
}
} finally {
if (obsClient != null) {
try {
obsClient.close();
} catch (IOException e) {
}
}
}
}
}

View File

@@ -14,6 +14,3 @@ knife4j:
- blade-admin - blade-admin
- blade-gateway - blade-gateway
- blade-log - blade-log
server:
port: 28080

View File

@@ -0,0 +1,22 @@
<?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">
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-service-api</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-file-api</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-ocr-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,22 @@
package org.springblade.file.callback;
/**
* 解压回调接口
* @author bfhuange
* @date 2024/11/8
*/
public interface UnzipCallback {
/**
* 解压任务的业务编码
* @return
*/
String getBizCode();
/**
* 回调接口
* @param bizId 业务id
* @param event 事件 org.springblade.file.callback.UnzipEventType name
* @return
*/
boolean callback(Long bizId, String event);
}

View File

@@ -0,0 +1,22 @@
package org.springblade.file.callback;
/**
* 解压事件类型
* @author bfhuange
* @date 2024/11/15
*/
public enum UnzipEventType {
/**
* 开始解压
*/
START,
/**
* 解压成功
*/
SUCCESS,
/**
* 解压失败
*/
FAIL,
;
}

View File

@@ -0,0 +1,20 @@
package org.springblade.file.config;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
/**
* @author bfhuange
* @since 2025/4/9
*/
public class FeignFileUploadClientConfig {
@Bean
public Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}

View File

@@ -0,0 +1,189 @@
package org.springblade.file.feign;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springblade.core.launch.constant.AppConstant;
import org.springblade.core.tool.api.FR;
import org.springblade.file.pojo.dto.*;
import org.springblade.file.pojo.entity.Attachment;
import org.springblade.file.pojo.vo.*;
import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.validation.annotation.Validated;
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 java.util.List;
/**
* @author bfhuange
* @date 2024/10/21
*/
@FeignClient(
value = AppConstant.APPLICATION_FILE_NAME,
fallback = IFileClientFallback.class
)
public interface IFileClient {
String API_PREFIX = "/feign/client/file";
String ATTACHMENT = API_PREFIX + "/attachment";
String ATTACHMENT_DETAIL = API_PREFIX + "/attachment-detail";
String BUSINESS_ATTACHMENT_SAVE_REMOVE = API_PREFIX + "/businessAttachment-save-remove";
String BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE = API_PREFIX + "/businessAttachment-batch-save-remove";
String BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE_BY_PARAM = API_PREFIX + "/businessAttachment-batch-save-remove-by-param";
String BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE_NO_BIZ_ID = API_PREFIX + "/businessAttachment-batch-save-remove-no-bizId";
String BUSINESS_ATTACHMENT_REMOVE = API_PREFIX + "/businessAttachment-remove";
String BUSINESS_ATTACHMENT_LIST = API_PREFIX + "/businessAttachment-list";
String BUSINESS_UNZIP_TASK_SAVE = API_PREFIX + "/businessUnzipTask-save";
String BUSINESS_UNZIP_TASK_UPDATE = API_PREFIX + "/businessUnzipTask-update";
String BUSINESS_UNZIP_TASK_CANCEL = API_PREFIX + "/businessUnzipTask-cancel";
String BUSINESS_UNZIP_TASK_REMOVE = API_PREFIX + "/businessUnzipTask-remove";
String BUSINESS_UNZIP_TASK_LIST = API_PREFIX + "/businessUnzipTask-list";
String BUSINESS_UNZIP_DETAIL_LIST = API_PREFIX + "/businessUnzipDetail-list";
String RECOGNITION_TRANSPORT_CERTIFICATE = API_PREFIX + "/recognitionTransportCertificate";
String FILE_URLS = API_PREFIX + "/getFileUrls";
/**
* 获取附件多个附件id用逗号分割
*
* @param id
* @return List<Attachment>
*/
@GetMapping(ATTACHMENT)
FR<List<Attachment>> getAttachment(@RequestParam("id") String id);
/**
* 获取附件详情
*
* @param id
* @return List<Attachment>
*/
@GetMapping(ATTACHMENT_DETAIL)
FR<AttachmentDetailVO> getAttachmentDetail(@RequestParam("id") Long id);
/**
* 保存并删除业务附件remove为true先用bizId删除再新增
*
* @param param
* @return
*/
@PostMapping(BUSINESS_ATTACHMENT_SAVE_REMOVE)
FR saveAndRemoveBusinessAttachment(@RequestBody @Validated BusinessAttachmentBatchAddDTO param);
/**
* 批量保存并删除业务附件先删除附件再新增业务id为空从params中获取bizId先删除
*
* @param params 参数为空不新增
* @param bizId 业务id
* @return
*/
@PostMapping(BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE)
FR batchSaveAndRemoveBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params, @RequestParam(value = "bizId", required = false) Long bizId);
/**
* 批量保存并删除业务附件deleteParam为空从params获取bizId删除不为空使用bizIds和bizCodes删除附件bizCodes为空删除所有bizIds的附件
* deleteParam 中 delete 为false不删除
*
* @param params 参数为空不新增
* @param deleteParam 删除参数,支持只删除业务编码参数的附件
* @return
*/
@PostMapping(BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE_BY_PARAM)
FR batchSaveAndRemoveByParamBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params, @Valid @SpringQueryMap BusinessAttachmentDeleteDTO deleteParam);
/**
* 批量保存并删除业务附件从参数中提取所有bizId
*
* @param params 参数
* @return
*/
@PostMapping(BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE_NO_BIZ_ID)
FR batchSaveAndRemoveBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params);
/**
* 根据业务id删除业务附件
*
* @param bizId
* @return
*/
@PostMapping(BUSINESS_ATTACHMENT_REMOVE)
FR removeBusinessAttachment(@NotNull(message = "业务id不能为空") @RequestParam(value = "bizId", required = false) Long bizId);
/**
* 查询业务附件列表
*
* @param param
* @return
*/
@PostMapping(BUSINESS_ATTACHMENT_LIST)
FR<List<BusinessAttachmentVO>> getBusinessAttachments(@Validated @RequestBody BusinessAttachmentQueryDTO param);
/**
* 保存业务解压任务
* @param param
* @return
*/
@PostMapping(BUSINESS_UNZIP_TASK_SAVE)
FR saveBusinessUnzipTask(@Validated @RequestBody BusinessUnzipTaskAddDTO param);
/**
* 更新业务解压任务(重新上传)
* @param param
* @return
*/
@PostMapping(BUSINESS_UNZIP_TASK_UPDATE)
FR updateBusinessUnzipTask(@Validated @RequestBody BusinessUnzipTaskUpdateDTO param);
/**
* 取消业务解压任务
* @param bizId
* @return
*/
@PostMapping(BUSINESS_UNZIP_TASK_CANCEL)
FR cancelBusinessUnzipTask(@NotNull(message = "业务id不能为空") @RequestParam("bizId") Long bizId);
/**
* 删除业务解压任务
* @param bizId
* @return
*/
@PostMapping(BUSINESS_UNZIP_TASK_REMOVE)
FR removeBusinessUnzipTask(@NotNull(message = "业务id不能为空") @RequestParam("bizId") Long bizId);
/**
* 根据业务id列表查询业务解压任务列表
* @param bizIds
* @return
*/
@PostMapping(BUSINESS_UNZIP_TASK_LIST)
FR<List<BusinessUnzipTaskListVO>> getBusinessUnzipTasksByBizIds(@Validated @NotEmpty(message = "业务id列表不能为空") @RequestBody List<Long> bizIds);
/**
* 查询解压明细列表
* @param bizId
* @return
*/
@GetMapping(BUSINESS_UNZIP_DETAIL_LIST)
FR<List<BusinessUnzipDetailListVO>> queryBusinessUnzipDetail(@NotNull(message = "业务id不能为空") @RequestParam("bizId") Long bizId);
/**
* 识别车辆运输凭证
*
* @param param 同一车牌文件夹下的附件objectKey列表
* @return
*/
@PostMapping(RECOGNITION_TRANSPORT_CERTIFICATE)
FR<TransportCertificateVO> recognitionTransportCertificates(@Validated @RequestBody FileCertificateBatchRecognitionDTO param);
/**
* 批量获取附件下载地址
* @param param
* @return
*/
@PostMapping(FILE_URLS)
FR<List<FileUrlVO>> getFileUrls(@Validated @RequestBody IdsDTO param);
}

View File

@@ -0,0 +1,113 @@
package org.springblade.file.feign;
import jakarta.validation.Valid;
import org.springblade.core.tool.api.FR;
import org.springblade.file.pojo.dto.*;
import org.springblade.file.pojo.entity.Attachment;
import org.springblade.file.pojo.vo.*;
import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* @author bfhuange
* @date 2024/10/21
*/
@Component
public class IFileClientFallback implements IFileClient, IFileUploadClient {
@Override
public FR<List<Attachment>> getAttachment(String id) {
return FR.fail("获取数据失败");
}
@Override
public FR<AttachmentDetailVO> getAttachmentDetail(Long id) {
return FR.fail("获取数据失败");
}
@Override
public FR saveAndRemoveBusinessAttachment(BusinessAttachmentBatchAddDTO param) {
return FR.fail("获取数据失败");
}
@Override
public FR batchSaveAndRemoveBusinessAttachment(List<BusinessAttachmentAddDTO> params, Long bizId) {
return FR.fail("获取数据失败");
}
@Override
public FR batchSaveAndRemoveByParamBusinessAttachment(List<@Valid BusinessAttachmentAddDTO> params, @Valid @SpringQueryMap BusinessAttachmentDeleteDTO deleteParam) {
return FR.fail("获取数据失败");
}
@Override
public FR batchSaveAndRemoveBusinessAttachment(List<@Valid BusinessAttachmentAddDTO> params) {
return FR.fail("获取数据失败");
}
@Override
public FR removeBusinessAttachment(Long bizId) {
return FR.fail("获取数据失败");
}
@Override
public FR<List<BusinessAttachmentVO>> getBusinessAttachments(BusinessAttachmentQueryDTO param) {
return FR.fail("获取数据失败");
}
@Override
public FR saveBusinessUnzipTask(BusinessUnzipTaskAddDTO param) {
return FR.fail("获取数据失败");
}
@Override
public FR updateBusinessUnzipTask(BusinessUnzipTaskUpdateDTO param) {
return FR.fail("获取数据失败");
}
@Override
public FR cancelBusinessUnzipTask(Long bizId) {
return FR.fail("获取数据失败");
}
@Override
public FR removeBusinessUnzipTask(Long bizId) {
return FR.fail("获取数据失败");
}
@Override
public FR<List<BusinessUnzipTaskListVO>> getBusinessUnzipTasksByBizIds(List<Long> bizIds) {
return FR.fail("获取数据失败");
}
@Override
public FR<List<BusinessUnzipDetailListVO>> queryBusinessUnzipDetail(Long bizId) {
return FR.fail("获取数据失败");
}
@Override
public FR<TransportCertificateVO> recognitionTransportCertificates(@Validated @RequestBody FileCertificateBatchRecognitionDTO param) {
return FR.fail("获取数据失败");
}
@Override
public FR<List<FileUrlVO>> getFileUrls(IdsDTO param) {
return FR.fail("获取数据失败");
}
@Override
public FR<List<Attachment>> ossUpload(MultipartFile[] files) {
return FR.fail("获取数据失败");
}
@Override
public FR<TempFileUrlVO> ossUploadTemp(MultipartFile file, Integer expires) {
return FR.fail("获取数据失败");
}
}

View File

@@ -0,0 +1,49 @@
package org.springblade.file.feign;
import jakarta.validation.constraints.NotNull;
import org.springblade.core.launch.constant.AppConstant;
import org.springblade.core.tool.api.FR;
import org.springblade.file.config.FeignFileUploadClientConfig;
import org.springblade.file.pojo.entity.Attachment;
import org.springblade.file.pojo.vo.TempFileUrlVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* @author bfhuange
* @date 2024/10/21
*/
@FeignClient(
value = AppConstant.APPLICATION_FILE_NAME,
fallback = IFileClientFallback.class,
configuration = FeignFileUploadClientConfig.class
)
public interface IFileUploadClient {
String API_PREFIX = "/feign/client/file";
String UPLOAD = API_PREFIX + "/ossUpload";
String UPLOAD_TEMP = API_PREFIX + "/ossUploadTemp";
/**
* 文件上传
* @param files
* @return
*/
@PostMapping(value = UPLOAD, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
FR<List<Attachment>> ossUpload(@RequestPart("files") MultipartFile[] files);
/**
* 上传临时文件,不保存到附件表,过期后自动删除
* @param file
* @param expires 过期时间,单位天,过期后自动删除
* @return
*/
@PostMapping(value = UPLOAD_TEMP, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
FR<TempFileUrlVO> ossUploadTemp(@RequestPart("file") MultipartFile file, @NotNull(message = "过期时间不能为空") @RequestParam(name = "expires", required = false) Integer expires);
}

View File

@@ -0,0 +1,53 @@
package org.springblade.file.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 业务附件信息表 数据传输对象实体类
*
* @author BladeX
* @since 2024-09-04
*/
@Data
@Schema(description = "业务附件新增参数")
@Builder
public class BusinessAttachmentAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 业务id
*/
@NotNull(message = "业务id不能为空")
@Schema(description = "业务id")
private Long bizId;
/**
* 业务code
*/
@NotBlank(message = "业务code不能为空")
@Schema(description = "业务code")
private String bizCode;
/**
* 附件id
*/
@NotNull(message = "附件id不能为空")
@Schema(description = "附件id")
private Long attachmentId;
/**
* 附件描述
*/
@Schema(description = "附件描述")
private String desc;
/**
* 排序
*/
@Schema(description = "排序")
private Integer sort;
}

View File

@@ -0,0 +1,73 @@
package org.springblade.file.pojo.dto;
import com.google.common.collect.Lists;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 业务附件信息表 数据传输对象实体类
*
* @author BladeX
* @since 2024-09-04
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Schema(description = "业务附件批量新增参数")
public class BusinessAttachmentBatchAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 业务id
*/
@NotNull(message = "业务id不能为空")
@Schema(description = "业务id")
private Long bizId;
/**
* 明细列表
*/
@Schema(description = "明细列表")
private List<@Valid BusinessAttachmentBatchAddDetailDTO> details;
/**
* 是否先删除,默认 是
*/
@Schema(description = "是否先删除,默认 是")
private boolean remove = true;
public BusinessAttachmentBatchAddDTO(Long bizId) {
this.bizId = bizId;
}
public BusinessAttachmentBatchAddDTO(Long bizId, String bizCode, Long attachmentId) {
this.bizId = bizId;
this.details = Lists.newArrayList(new BusinessAttachmentBatchAddDetailDTO(bizCode, attachmentId));
}
public BusinessAttachmentBatchAddDTO addDetail(BusinessAttachmentBatchAddDetailDTO detail) {
if (this.details == null) {
this.details = new ArrayList<>();
}
this.details.add(detail);
return this;
}
public BusinessAttachmentBatchAddDTO addDetail(String bizCode, Long attachmentId) {
return this.addDetail(new BusinessAttachmentBatchAddDetailDTO(bizCode, attachmentId));
}
public BusinessAttachmentBatchAddDTO addDetail(String bizCode, List<Long> attachmentIds) {
return this.addDetail(new BusinessAttachmentBatchAddDetailDTO(bizCode, attachmentIds));
}
}

View File

@@ -0,0 +1,48 @@
package org.springblade.file.pojo.dto;
import com.google.common.collect.Lists;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 业务附件信息表 数据传输对象实体类
*
* @author BladeX
* @since 2024-09-04
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Schema(description = "业务附件批量新增明细参数")
@Builder
public class BusinessAttachmentBatchAddDetailDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 业务code
*/
@NotBlank(message = "业务编码不能为空")
@Schema(description = "业务code")
private String bizCode;
/**
* 附件id列表
*/
@NotEmpty(message = "附件id列表不能为空")
@Schema(description = "附件id列表")
private List<@NotNull(message = "附件id不能为空") Long> attachmentIds;
public BusinessAttachmentBatchAddDetailDTO(String bizCode, Long attachmentId) {
this.bizCode = bizCode;
this.attachmentIds = Lists.newArrayList(attachmentId);
}
}

View File

@@ -0,0 +1,54 @@
package org.springblade.file.pojo.dto;
import com.google.common.collect.Lists;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springblade.core.tool.utils.StringUtil;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 业务附件删除参数
* @author bfhuange
* @since 2024/12/3
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Schema(description = "业务附件删除参数")
public class BusinessAttachmentDeleteDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 业务id列表
*/
@Schema(description = "业务id列表")
private List<Long> bizIds;
/**
* 业务code列表
*/
@Schema(description = "业务code列表")
private List<String> bizCodes;
/**
* 是否删除
*/
@Schema(description = "是否删除")
private boolean delete = true;
public BusinessAttachmentDeleteDTO(Long bizId) {
this.bizIds = Lists.newArrayList(bizId);
}
public BusinessAttachmentDeleteDTO(Long bizId, String bizCode) {
this.bizIds = Lists.newArrayList(bizId);
if (StringUtil.isNotBlank(bizCode)) {
this.bizCodes = Lists.newArrayList(bizCode);
}
}
}

View File

@@ -0,0 +1,62 @@
package org.springblade.file.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 业务附件信息表 数据传输对象实体类
*
* @author BladeX
* @since 2024-09-04
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
@Schema(description = "业务附件查询参数")
public class BusinessAttachmentQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 业务id列表
*/
@NotNull(message = "业务id列表不能为空")
@Schema(description = "业务id列表")
private List<@NotNull(message = "业务id不能为空") Long> bizIds;
/**
* 业务code
*/
@Schema(description = "业务code")
private String bizCode;
/**
* 业务code列表
*/
@Schema(description = "业务code列表")
private List<String> bizCodes;
public BusinessAttachmentQueryDTO(Long bizId) {
this.bizIds = List.of(bizId);
}
public BusinessAttachmentQueryDTO(List<Long> bizIds) {
this.bizIds = bizIds;
}
/**
* 兼容历史参数
* @param bizId
*/
public void setBizId(Long bizId) {
if (this.bizIds == null) {
this.bizIds = new ArrayList<>();
}
this.bizIds.add(bizId);
}
}

View File

@@ -0,0 +1,55 @@
package org.springblade.file.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springblade.file.pojo.enums.UnzipTask;
import java.io.Serial;
import java.io.Serializable;
/**
* 业务解压任务新增参数
* @author bfhuange
* @date 2024/10/28
*/
@Data
@Schema(description = "业务解压任务新增参数")
public class BusinessUnzipTaskAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 业务id
*/
@NotNull(message = "业务id不能为空")
@Schema(description = "业务id")
private Long bizId;
/**
* 部门名称ID
*/
@Schema(description = "部门名称ID")
private Long departmentId;
/**
* 业务code
*/
@NotBlank(message = "业务code不能为空")
@Schema(description = "业务code")
private String bizCode = UnzipTask.BizCode.WAYBILL.getCode();
/**
* 文档编号
*/
@Schema(description = "文档编号")
private String docCode;
/**
* 关联类型file_task 文件任务attachment 附件
*/
@Schema(description = "关联类型file_task 文件任务attachment 附件")
private String relateType = UnzipTask.RelateType.FILE_TASK.getCode();
/**
* 关联id文件任务id或附件id
*/
@NotNull(message = "关联id不能为空")
@Schema(description = "关联id文件任务id或附件id")
private Long relateId;
}

View File

@@ -0,0 +1,55 @@
package org.springblade.file.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 业务解压任务查询参数
* @author bfhuange
* @date 2024/10/28
*/
@Data
@Schema(description = "业务解压任务查询参数")
public class BusinessUnzipTaskQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 文档编号(凭证批次号)
*/
@Schema(description = "文档编号(凭证批次号)")
private String docCode;
/**
* 附件名称
*/
@Schema(description = "附件名称")
private String attachmentName;
/**
* 状态 uploading:正在上传 completed上传完成 failed上传失败 paused已暂停
*/
@Schema(description = "状态 uploading:正在上传 completed上传完成 failed上传失败 paused已暂停")
private String status;
/**
* 关联类型file_task 文件任务attachment 附件
*/
@Schema(description = "关联类型file_task 文件任务attachment 附件", hidden = true)
private String relateType;
/**
* 是否只查询当前用户
*/
@Schema(description = "是否只查询当前用户")
private boolean currentUser;
/**
* 当前登录人id
*/
@Schema(hidden = true)
private Long currentUserId;
/**
* 业务id列表
*/
@Schema(description = "业务id列表", hidden = true)
private List<Long> bizIds;
}

View File

@@ -0,0 +1,42 @@
package org.springblade.file.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springblade.file.pojo.enums.UnzipTask;
import java.io.Serial;
import java.io.Serializable;
/**
* 业务解压任务更新参数
* @author bfhuange
* @date 2024/10/28
*/
@Data
@Schema(description = "业务解压任务更新参数")
public class BusinessUnzipTaskUpdateDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@Schema(description = "主键")
private Long id;
/**
* 业务id
*/
@Schema(description = "业务id")
private Long bizId;
/**
* 关联id文件任务id或附件id
*/
@NotNull(message = "关联id不能为空")
@Schema(description = "关联id文件任务id或附件id")
private Long relateId;
/**
* 关联类型file_task 文件任务attachment 附件
*/
@Schema(description = "关联类型file_task 文件任务attachment 附件", hidden = true)
private String relateType = UnzipTask.RelateType.FILE_TASK.getCode();
}

View File

@@ -0,0 +1,44 @@
package org.springblade.file.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 凭证识别参数,依次调用单张识别
* @author bfhuange
* @since 2024/12/13
*/
@Data
@Schema(description = "凭证附件参数")
public class CertificateAttachmentDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 磅单ObjectKey
*/
@Schema(description = "磅单ObjectKey")
private String poundObjectKey;
/**
* 车头ObjectKey
*/
@Schema(description = "车头ObjectKey")
private String carFrontObjectKey;
/**
* 驾驶证ObjectKey
*/
@Schema(description = "驾驶证ObjectKey")
private String driverLicenseObjectKey;
/**
* 行驶证ObjectKey
*/
@Schema(description = "行驶证ObjectKey")
private String vehicleLicenseObjectKey;
/**
* 项目简称
*/
@Schema(description = "项目简称")
private String projectAbbreviation;
}

View File

@@ -0,0 +1,45 @@
package org.springblade.file.pojo.dto;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 凭证批量识别参数,所有图像链接统一识别,不分类
* @author bfhuange
* @since 2025/3/13
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class FileCertificateBatchRecognitionDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 所有图像 obs key列表
*/
@NotEmpty(message = "图像 obs key列表不能为空")
private List<String> objectKeys;
/**
* 项目简称
*/
private String projectAbbreviation;
/**
* 凭证编码
*/
private String code;
/**
* 目录名称
*/
private String dirName;
public FileCertificateBatchRecognitionDTO(List<String> objectKeys) {
this.objectKeys = objectKeys;
}
}

View File

@@ -0,0 +1,62 @@
package org.springblade.file.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 文件任务创建对象
* @author bfhuange
* @date 2024/10/22
*/
@Data
@Schema(description = "文件任务创建对象")
public class FileTaskCreateDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* Md5
*/
@NotBlank(message = "Md5不能为空")
@Schema(description = "Md5")
private String md5;
/**
* 附件名
*/
@NotBlank(message = "附件名不能为空")
@Schema(description = "附件名")
private String attachmentName;
/**
* 附件大小(单位:B)
*/
@NotNull(message = "附件大小不能为空")
@Schema(description = "附件大小(单位:B)")
private Long size;
/**
* 分段大小
*/
@NotNull(message = "分段大小不能为空")
@Schema(description = "分段大小")
private Integer chunkSize;
/**
* 分段数量
*/
@NotNull(message = "分段数量不能为空")
@Schema(description = "分段数量")
private Integer chunkTotal;
/**
* 是否强制创建新任务,默认 false如果存在相同md5的任务会创建失败true则新创建一个任务
*/
@Schema(description = "是否强制创建新任务,默认 false如果存在相同md5的任务会创建失败true则新创建一个任务")
private boolean force;
}

View File

@@ -0,0 +1,41 @@
package org.springblade.file.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springblade.file.pojo.vo.PartEtagVO;
import java.io.Serial;
import java.io.Serializable;
/**
* 文件任务更新对象
* @author bfhuange
* @date 2024/10/22
*/
@Data
@Schema(description = "文件任务更新对象")
public class FileTaskUpdateDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 文件任务id
*/
@NotNull(message = "文件任务id不能为空")
@Schema(description = "主键id")
private Long id;
/**
* 段的ETag值。分段的Base64编码的128位MD5摘要长度为32的字符串
*/
@NotBlank(message = "etag不能为空")
@Schema(description = "etag")
private String etag;
/**
* 段号。分段号可以是不连续的。取值范围是[1,10000]的非负整数
*/
@NotNull(message = "段号不能为空")
@Schema(description = "段号")
private Integer partNumber;
}

View File

@@ -0,0 +1,15 @@
package org.springblade.file.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
@Data
@Schema(description = "分段上传文件对象")
public class FileUploadDTO {
private MultipartFile multipartFile; // 文件对象
private String md5; // 文件md5值
private Long chunkTotal; // 分段总数
private Long chunkSize; // 分段大小
private Integer index; // 当前分段的位置
}

View File

@@ -0,0 +1,33 @@
package org.springblade.file.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* id批量参数
* @author bfhuange
* @date 2024/10/29
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
@Schema(description = "id批量参数")
public class IdsDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id列表
*/
@NotEmpty(message = "id列表不能为空")
@Schema(description = "id列表")
private @Valid List<@NotNull(message = "id不能为空") Long> ids;
}

View File

@@ -0,0 +1,111 @@
package org.springblade.file.pojo.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
/**
* @author zhaowei
* @date 2024/8/16
*/
@Data
@TableName("blade_attachment")
@Schema(description = "附件对象")
public class Attachment implements Serializable {
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "主键id")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 附件名
*/
@Schema(description = "附件名")
private String attachmentName;
/**
* 压缩文件所在压缩包相对路径
*/
@Schema(description = "压缩文件所在压缩包相对路径")
private String dir;
/**
* 附件大小(单位:B)
*/
@Schema(description = "附件大小(单位:B)")
private long size;
/**
* objectKey
*/
@Schema(description = "华为OBS objectKey")
private String objectKey;
/**
* 创建人-用户名称
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人-用户名称")
@TableField(fill = FieldFill.INSERT)
private String createUserName;
/**
* 创建人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人")
@TableField(fill = FieldFill.INSERT)
private Long createUser;
/**
* 创建时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新人-用户名称
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人-用户名称")
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateUserName;
/**
* 更新人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "更新人")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
/**
* 更新时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 状态[0:未删除,1:删除]
*/
@TableLogic
@Schema(description = "是否已删除 [0:未删除,1:删除]")
private Integer isDeleted;
}

View File

@@ -0,0 +1,113 @@
package org.springblade.file.pojo.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 业务附件信息表 实体类
*
* @author BladeX
* @since 2024-09-04
*/
@Data
@TableName("blade_business_attachment")
@Schema(description = "BusinessAttachment对象")
public class BusinessAttachment implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 业务id
*/
@Schema(description = "业务id")
private Long bizId;
/**
* 业务code
*/
@Schema(description = "业务code")
private String bizCode;
/**
* 附件id
*/
@Schema(description = "附件id")
private Long attachmentId;
/**
* 附件描述
*/
@Schema(description = "附件描述")
@TableField(value = "`desc`")
private String desc;
/**
* 排序
*/
@Schema(description = "排序")
private Integer sort;
/**
* 租户ID
*/
@Schema(description = "租户ID")
@TableField(fill = FieldFill.INSERT)
private String tenantId;
/**
* 主键id
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "主键id")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 创建人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人", hidden = true)
@TableField(fill = FieldFill.INSERT)
private Long createUser;
/**
* 创建时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "创建时间", hidden = true)
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "更新人", hidden = true)
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
/**
* 更新时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "更新时间", hidden = true)
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 状态[0:未删除,1:删除]
*/
@TableLogic
@Schema(description = "是否已删除", hidden = true)
@TableField(fill = FieldFill.INSERT)
private Integer isDeleted;
}

View File

@@ -0,0 +1,85 @@
package org.springblade.file.pojo.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 业务解压明细表
* </p>
*
* @author bfhuange
* @since 2024-10-30
*/
@Data
@TableName("blade_business_unzip_detail")
@Schema(description = "BusinessUnzipDetail对象")
public class BusinessUnzipDetail implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "主键")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 业务解压任务id
*/
@Schema(description = "业务解压任务id")
private Long businessUnzipTaskId;
/**
* 附件id
*/
@Schema(description = "附件id")
private Long attachmentId;
/**
* 创建人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人")
@TableField(fill = FieldFill.INSERT)
private Long createUser;
/**
* 创建时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "更新人")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
/**
* 更新时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 状态[0:未删除,1:删除]
*/
@TableLogic
@Schema(description = "是否已删除 [0:未删除,1:删除]")
@TableField(fill = FieldFill.INSERT)
private Integer isDeleted;
}

View File

@@ -0,0 +1,89 @@
package org.springblade.file.pojo.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 业务解压任务表
* </p>
*
* @author bfhuange
* @since 2024-10-28
*/
@Data
@TableName("blade_business_unzip_task")
@Schema(description = "业务解压任务表")
public class BusinessUnzipTask implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private Long id;
@Schema(description = "业务id")
private Long bizId;
/**
* 部门名称ID
*/
@Schema(description = "部门名称ID")
private Long departmentId;
@Schema(description = "业务code")
private String bizCode;
@Schema(description = "文档编号")
private String docCode;
@Schema(description = "关联类型file_task 文件任务attachment 附件")
private String relateType;
@Schema(description = "关联id文件任务id或附件id")
private Long relateId;
@Schema(description = "状态 uploading正在上传 unzipping解压中 finished已完成")
private String status;
@Schema(description = "租户ID")
@TableField(fill = FieldFill.INSERT)
private String tenantId;
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人", hidden = true)
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "创建时间", hidden = true)
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "更新人", hidden = true)
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "更新时间", hidden = true)
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@Schema(description = "是否已删除", hidden = true)
@TableField(fill = FieldFill.INSERT)
@TableLogic
private Integer isDeleted;
}

View File

@@ -0,0 +1,152 @@
package org.springblade.file.pojo.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* @author zhaowei
* @date 2024/8/23
*/
@Data
@TableName("blade_file_task")
@Schema(description = "文件上传任务对象")
public class FileTask implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "主键id")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* Md5
*/
@Schema(description = "Md5")
private String md5;
/**
* OBS objectKey
*/
@Schema(description = "OBS objectKey")
private String objectKey;
/**
* 附件名
*/
@Schema(description = "附件名")
private String attachmentName;
/**
* 文件类型
*/
@Schema(description = "文件类型")
private String suffix;
/**
* 附件大小(单位:B)
*/
@Schema(description = "附件大小(单位:B)")
private Long size;
/**
* 分段大小
*/
@Schema(description = "分段大小")
private Integer chunkSize;
/**
* 分段数量
*/
@Schema(description = "分段数量")
private Integer chunkTotal;
/**
* 当前分段的位置(已上传完成的分段)从0开始上传完第1段为1
*/
@Schema(description = "当前分段的位置(已上传完成的分段)从0开始上传完第1段为1")
private Integer currentIndex;
/**
* 华为云初始化上传任务生成的uploadId
*/
@Schema(description = "华为云初始化上传任务生成的uploadId")
private String uploadId;
/**
* 状态 uploading:正在上传 completed上传完成 failed上传失败 paused已暂停
*/
@Schema(description = "状态 uploading:正在上传 completed上传完成 failed上传失败 paused已暂停")
private String status;
/**
* 创建人-用户名称
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人-用户名称")
@TableField(fill = FieldFill.INSERT)
private String createUserName;
/**
* 创建人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人")
@TableField(fill = FieldFill.INSERT)
private Long createUser;
/**
* 创建时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新人-用户名称
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人-用户名称")
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateUserName;
/**
* 更新人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "更新人")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
/**
* 更新时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 状态[0:未删除,1:删除]
*/
@TableLogic
@Schema(description = "是否已删除 [0:未删除,1:删除]")
@TableField(fill = FieldFill.INSERT)
private Integer isDeleted;
}

View File

@@ -0,0 +1,69 @@
package org.springblade.file.pojo.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* @author zhaowei
* @date 2024/8/16
*/
@Data
@TableName("blade_file_task_part")
@Schema(description = "文件任务分段表")
public class FileTaskPart implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "主键id")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 文件任务Id
*/
@Schema(description = "文件任务Id")
private Long fileTaskId;
/**
* 段号。分段号可以是不连续的。取值范围是[1,10000]的非负整数
*/
@Schema(description = "段号。分段号可以是不连续的。取值范围是[1,10000]的非负整数")
private Integer partNumber;
/**
* 段的ETag值。分段的Base64编码的128位MD5摘要长度为32的字符串
*/
@Schema(description = "段的ETag值。分段的Base64编码的128位MD5摘要长度为32的字符串")
private String etag;
/**
* 创建人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人")
@TableField(fill = FieldFill.INSERT)
private Long createUser;
/**
* 创建时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
}

View File

@@ -0,0 +1,39 @@
package org.springblade.file.pojo.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 文件任务状态
* @author bfhuange
* @date 2024/10/22
*/
@AllArgsConstructor
@Getter
public enum FileTaskStatus {
/**
* 正在上传
*/
UPLOADING("uploading", "正在上传"),
/**
* 上传完成
*/
COMPLETED("completed", "上传完成"),
/**
* 上传失败
*/
FAILED("failed", "上传失败"),
/**
* 已暂停
*/
PAUSED("paused", "已暂停"),
;
/**
* 编码
*/
private final String code;
/**
* 名称
*/
private final String name;
}

View File

@@ -0,0 +1,99 @@
package org.springblade.file.pojo.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 解压任务枚举类
* @author bfhuange
* @date 2024/10/27
*/
public class UnzipTask {
/**
* 业务编码枚举
*/
@AllArgsConstructor
@Getter
public enum BizCode {
/**
* 运单
*/
WAYBILL("waybill", "运单"),
/**
* 基地
*/
WAREHOUSE("warehouse", "基地"),
;
/**
* 编码
*/
private final String code;
/**
* 名称
*/
private final String name;
}
/**
* 关联类型枚举
*/
@AllArgsConstructor
@Getter
public enum RelateType {
/**
* 文件任务
*/
FILE_TASK("file_task", "文件任务"),
/**
* 附件
*/
ATTACHMENT("attachment", "附件"),
;
/**
* 编码
*/
private final String code;
/**
* 名称
*/
private final String name;
}
/**
* 状态枚举
*/
@AllArgsConstructor
@Getter
public enum Status {
/**
* 正在上传
*/
UPLOADING("uploading", "正在上传"),
/**
* 上传完成
*/
UPLOADED("uploaded", "上传完成"),
/**
* 正在解压
*/
UNZIPPING("unzipping", "正在解压"),
/**
* 解压失败
*/
UNZIP_FAIL("unzip_fail", "解压失败"),
/**
* 已完成
*/
FINISHED("finished", "已完成"),
;
/**
* 编码
*/
private final String code;
/**
* 名称
*/
private final String name;
}
}

View File

@@ -0,0 +1,73 @@
package org.springblade.file.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 附件详情对象
* @author bfhuange
* @date 2024/10/30
*/
@Data
@Schema(description = "附件详情对象")
public class AttachmentDetailVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
@Schema(description = "主键id")
private Long id;
/**
* 附件名
*/
@Schema(description = "附件名")
private String attachmentName;
/**
* objectKey
*/
@Schema(description = "华为OBS objectKey")
private String objectKey;
/**
* 附件大小(单位:B)
*/
@Schema(description = "附件大小(单位:B)")
private long size;
/**
* 创建人
*/
@Schema(description = "创建人")
private Long createUser;
/**
* 创建时间
*/
@Schema(description = "创建时间")
private Date createTime;
/**
* 更新人
*/
@Schema(description = "更新人")
private Long updateUser;
/**
* 更新时间
*/
@Schema(description = "更新时间")
private Date updateTime;
/**
* 下载链接
*/
@Schema(description = "下载链接")
private String url;
}

View File

@@ -0,0 +1,105 @@
package org.springblade.file.pojo.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 业务附件信息表 视图实体类
*
* @author BladeX
* @since 2024-09-04
*/
@Data
@Schema(description = "业务附件响应参数")
public class BusinessAttachmentVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "主键id")
private Long id;
/**
* 业务id
*/
@Schema(description = "业务id")
@JsonSerialize(using = ToStringSerializer.class)
private Long bizId;
/**
* 业务code
*/
@Schema(description = "业务code")
private String bizCode;
/**
* 业务code名称
*/
@Schema(description = "业务code名称")
private String bizCodeStr;
/**
* 附件id
*/
@Schema(description = "附件id")
@JsonSerialize(using = ToStringSerializer.class)
private Long attachmentId;
/**
* 附件名
*/
@Schema(description = "附件名")
private String attachmentName;
/**
* 附件大小(单位:B)
*/
@Schema(description = "附件大小(单位:B)")
private long size;
/**
* objectKey
*/
@Schema(description = "华为OBS objectKey")
private String objectKey;
/**
* 附件描述
*/
@Schema(description = "附件描述")
@Size(max = 100, message = "附件描述长度不能超过100")
private String desc;
/**
* 排序
*/
@Schema(description = "排序")
private Integer sort;
/**
* 上传人id
*/
@Schema(description = "上传人id")
@JsonSerialize(using = ToStringSerializer.class)
private Long uploadUserId;
/**
* 上传人姓名
*/
@Schema(description = "上传人姓名")
private String uploadUserName;
/**
* 上传人姓名
*/
@Schema(description = "上传人姓名")
private String createUserName;
/**
* 创建时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_TIME_MINI)
@Schema(description = "创建时间")
private Date createTime;
}

View File

@@ -0,0 +1,94 @@
package org.springblade.file.pojo.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 业务解压任务明细对象
* @author bfhuange
* @date 2024/11/08
*/
@Data
@Schema(description = "业务解压任务明细对象")
public class BusinessUnzipDetailListVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "主键")
private Long id;
/**
* 附件id
*/
@Schema(description = "附件id")
@JsonSerialize(using = ToStringSerializer.class)
private Long attachmentId;
//==========================附件属性=============================
/**
* 压缩文件所在压缩包相对路径
*/
@Schema(description = "压缩文件所在压缩包相对路径")
private String dir;
/**
* 附件名
*/
@Schema(description = "附件名")
private String attachmentName;
/**
* 附件大小(单位:B)
*/
@Schema(description = "附件大小(单位:B)")
private long size;
/**
* objectKey
*/
@Schema(description = "华为OBS objectKey")
private String objectKey;
/**
* 附件描述
*/
@Schema(description = "附件描述")
@Size(max = 100, message = "附件描述长度不能超过100")
private String desc;
/**
* 排序
*/
@Schema(description = "排序")
private Integer sort;
/**
* 上传人id
*/
@Schema(description = "上传人id")
@JsonSerialize(using = ToStringSerializer.class)
private Long uploadUserId;
/**
* 上传人姓名
*/
@Schema(description = "上传人姓名")
private String uploadUserName;
/**
* 上传人姓名
*/
@Schema(description = "上传人姓名")
private String createUserName;
/**
* 创建时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_TIME_MINI)
@Schema(description = "创建时间")
private Date createTime;
}

View File

@@ -0,0 +1,169 @@
package org.springblade.file.pojo.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 业务解压任务分页对象
* @author bfhuange
* @date 2024/10/28
*/
@Data
@Schema(description = "业务解压任务分页对象")
public class BusinessUnzipTaskListVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "主键")
private Long id;
/**
* 业务id
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "业务id")
private Long bizId;
/**
* 业务code
*/
@Schema(description = "业务code")
private String bizCode;
/**
* 文档编号
*/
@Schema(description = "文档编号")
private String docCode;
/**
* 关联类型file_task 文件任务attachment 附件
*/
@Schema(description = "关联类型file_task 文件任务attachment 附件")
private String relateType;
/**
* 关联id文件任务id或附件id
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "关联id文件任务id或附件id")
private Long relateId;
//==========================文件任务属性=============================
/**
* Md5
*/
@Schema(description = "Md5")
private String md5;
/**
* OBS objectKey
*/
@Schema(description = "OBS objectKey")
private String objectKey;
/**
* 附件名
*/
@Schema(description = "附件名")
private String attachmentName;
/**
* 文件类型
*/
@Schema(description = "文件类型")
private String suffix;
/**
* 附件大小(单位:B)
*/
@Schema(description = "附件大小(单位:B)")
private Long size;
/**
* 分段大小
*/
@Schema(description = "分段大小")
private Integer chunkSize;
/**
* 分段数量
*/
@Schema(description = "分段数量")
private Integer chunkTotal;
/**
* 当前分段的位置(已上传完成的分段)从0开始上传完第1段为1
*/
@Schema(description = "当前分段的位置(已上传完成的分段)从0开始上传完第1段为1")
private Integer currentIndex;
/**
* 华为云初始化上传任务生成的uploadId
*/
@Schema(description = "华为云初始化上传任务生成的uploadId")
private String uploadId;
/**
* 状态 uploading:正在上传 completed上传完成 failed上传失败 paused已暂停
*/
@Schema(description = "状态 uploading:正在上传 completed上传完成 failed上传失败 paused已暂停")
private String status;
/**
* 状态名称
*/
@Schema(description = "状态名称")
private String statusStr;
/**
* 创建人-用户名称
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人-用户名称")
private String createUserName;
/**
* 创建人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人")
private Long createUser;
/**
* 创建时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "创建时间")
private Date createTime;
/**
* 更新人-用户名称
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人-用户名称")
private String updateUserName;
/**
* 更新人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "更新人")
private Long updateUser;
/**
* 更新时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "更新时间")
private Date updateTime;
}

View File

@@ -0,0 +1,36 @@
package org.springblade.file.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.File;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 文件处理结果对象
* @author bfhuange
* @date 2024/10/30
*/
@Data
@Schema(description = "文件处理结果对象")
public class FileHandleVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 下载的压缩文件
*/
@Schema(description = "下载的压缩文件")
private File downloadFile;
/**
* 解压目录
*/
@Schema(description = "解压目录")
private File unzippedDir;
/**
* 解压后的文件列表
*/
@Schema(description = "解压后的文件列表")
private List<File> unzippedFiles;
}

View File

@@ -0,0 +1,25 @@
package org.springblade.file.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 文件任务更新结果
* @author bfhuange
* @date 2024/10/28
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Schema(description = "文件任务更新结果")
public class FileTaskUpdateVO extends FileTaskVO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 文档编号(凭证批次号),如果有则返回,没有为空
*/
@Schema(description = "文档编号(凭证批次号),如果有则返回,没有为空")
private String docCode;
}

View File

@@ -0,0 +1,133 @@
package org.springblade.file.pojo.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 文件任务对象
* @author bfhuange
* @date 2024/10/22
*/
@Data
@Schema(description = "文件任务对象")
public class FileTaskVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "主键id")
private Long id;
/**
* Md5
*/
@Schema(description = "Md5")
private String md5;
/**
* OBS objectKey
*/
@Schema(description = "OBS objectKey")
private String objectKey;
/**
* 附件名
*/
@Schema(description = "附件名")
private String attachmentName;
/**
* 文件类型
*/
@Schema(description = "文件类型")
private String suffix;
/**
* 附件大小(单位:B)
*/
@Schema(description = "附件大小(单位:B)")
private Long size;
/**
* 分段大小
*/
@Schema(description = "分段大小")
private Integer chunkSize;
/**
* 分段数量
*/
@Schema(description = "分段数量")
private Integer chunkTotal;
/**
* 当前分段的位置(已上传完成的分段)从0开始上传完第1段为1
*/
@Schema(description = "当前分段的位置(已上传完成的分段)从0开始上传完第1段为1")
private Integer currentIndex;
/**
* 华为云初始化上传任务生成的uploadId
*/
@Schema(description = "华为云初始化上传任务生成的uploadId")
private String uploadId;
/**
* 状态 uploading:正在上传 completed上传完成 failed上传失败 paused已暂停
*/
@Schema(description = "状态 uploading:正在上传 completed上传完成 failed上传失败 paused已暂停")
private String status;
/**
* 创建人-用户名称
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人-用户名称")
private String createUserName;
/**
* 创建人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人")
private Long createUser;
/**
* 创建时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "创建时间")
private Date createTime;
/**
* 更新人-用户名称
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人-用户名称")
private String updateUserName;
/**
* 更新人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "更新人")
private Long updateUser;
/**
* 更新时间
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "更新时间")
private Date updateTime;
}

View File

@@ -0,0 +1,33 @@
package org.springblade.file.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 文件下载地址对象
* @author bfhuange
* @since 2025/4/22
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
@Schema(description = "文件下载地址对象")
public class FileUrlVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
@Schema(description = "主键id")
private Long id;
/**
* 文件下载地址
*/
@Schema(description ="文件下载地址")
private String url;
}

View File

@@ -0,0 +1,35 @@
package org.springblade.file.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 文件对象
* @author bfhuange
* @date 2024/11/19
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
@Schema(description = "文件对象")
public class FileVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* OBS objectKey
*/
@Schema(description = "OBS objectKey")
private String objectKey;
/**
* Md5
*/
@Schema(description = "Md5")
private String md5;
}

View File

@@ -0,0 +1,29 @@
package org.springblade.file.pojo.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 待合并的段
* @author bfhuange
* @date 2024/10/21
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class PartEtagVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 段的ETag值。分段的Base64编码的128位MD5摘要长度为32的字符串
*/
private String etag;
/**
* 段号。分段号可以是不连续的。取值范围是[1,10000]的非负整数
*/
private Integer partNumber;
}

View File

@@ -0,0 +1,33 @@
package org.springblade.file.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 文件下载地址对象
* @author bfhuange
* @since 2025/4/22
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
@Schema(description = "临时文件下载地址对象")
public class TempFileUrlVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* obs key
*/
@Schema(description = "obs key")
private String objectKey;
/**
* 文件下载地址
*/
@Schema(description ="文件下载地址")
private String url;
}

View File

@@ -0,0 +1,81 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.SqlCondition;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;
import java.io.Serial;
@Data
@Schema(description = "APP版本管理")
@TableName("t_app_version")
@EqualsAndHashCode(callSuper = true)
public class AppVersion extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
@NotEmpty
@Schema(description = "更新类型")
protected String updateType;
@TableField(condition = SqlCondition.LIKE)
@NotEmpty
@Schema(description = "版本号")
protected String version;
@TableField(condition = SqlCondition.LIKE)
@NotEmpty
@Schema(description = "子版本号")
protected String subVersion;
@NotEmpty
@Schema(description = "强制更新(是/否)")
protected String isForceUpdate;
@NotEmpty
@Schema(description = "安装方式")
protected String installMethod;
@NotNull
@Schema(description = "安装包(附件Id)")
protected Long installAttachmentId;
@TableField(condition = SqlCondition.LIKE)
@NotEmpty
@Schema(description = "更新说明")
protected String updateExplanation;
}

View File

@@ -23,6 +23,7 @@
<module>blade-system-api</module> <module>blade-system-api</module>
<module>blade-user-api</module> <module>blade-user-api</module>
<module>blade-record-api</module> <module>blade-record-api</module>
<module>blade-file-api</module>
</modules> </modules>
<dependencies> <dependencies>

View File

@@ -0,0 +1,19 @@
FROM bladex/alpine-java:openjdk17_cn_slim
ARG ACTIVE
LABEL maintainer="bladejava@qq.com"
# 设置时区为上海
ENV TZ=Asia/Shanghai
# 设置时区信息
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY ./target/blade-file.jar /opt/app.jar
WORKDIR /opt
EXPOSE 8107
ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/opt/logs", "-XX:ErrorFile=/opt/logs/hs_err_pid%p.log", "-Xlog:gc*:file=/opt/logs/gc.log:time,uptime,level,tags:filecount=5,filesize=50M", "-XX:MaxDirectMemorySize=512m", "-Xms1024m", "-Xmx4096m", "-jar", "app.jar"]
CMD ["--spring.profiles.active=${ACTIVE}","--logging.path=/opt/logs"]

View File

@@ -0,0 +1,19 @@
#FROM 10.38.16.129/bladex/alpine-java:openjdk17_cn_slim
#FROM registry.cn-hangzhou.aliyuncs.com/vic_person/alpine-java:openjdk17_cn_slim
FROM 10.38.16.129:80/bladex/alpine-java:openjdk17_cn_slim
ARG ACTIVE
LABEL maintainer="bladejava@qq.com"
RUN apk add --no-cache tzdata
# 设置时区为上海
ENV TZ=Asia/Shanghai
# 设置时区信息
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY ./target/*.jar /opt/app.jar
ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/opt/app.jar"]
CMD ["--spring.profiles.active=${ACTIVE}"]

View File

@@ -0,0 +1,19 @@
FROM bladex/alpine-java:openjdk17_cn_slim
ARG ACTIVE
LABEL maintainer="bladejava@qq.com"
# 设置时区为上海
ENV TZ=Asia/Shanghai
# 设置时区信息
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY ./target/blade-file.jar /opt/app.jar
WORKDIR /opt
EXPOSE 8107
ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/opt/logs", "-XX:ErrorFile=/opt/logs/hs_err_pid%p.log", "-Xlog:gc*:file=/opt/logs/gc.log:time,uptime,level,tags:filecount=5,filesize=50M", "-XX:MaxDirectMemorySize=512m", "-Xms1024m", "-Xmx1024m", "-jar", "app.jar"]
CMD ["--spring.profiles.active=${ACTIVE}","--logging.path=/opt/logs"]

View File

@@ -0,0 +1,139 @@
<?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">
<parent>
<artifactId>blade-service</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-file</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<properties>
<commons-compress.version>1.27.1</commons-compress.version>
<xz.version>1.10</xz.version>
<!-- 其他依赖版本 -->
<lombok.version>1.18.30</lombok.version>
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
<junrar.version>7.5.5</junrar.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-swagger</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-file-api</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-wps-api</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-ocr-api</artifactId>
</dependency>
<!--zip解压缩-->
<dependency>
<groupId>net.lingala.zip4j</groupId>
<artifactId>zip4j</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 解压 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>${commons-compress.version}</version>
</dependency>
<dependency>
<groupId>org.tukaani</groupId>
<artifactId>xz</artifactId>
<version>${xz.version}</version>
</dependency>
<dependency>
<groupId>com.github.junrar</groupId>
<artifactId>junrar</artifactId>
<version>${junrar.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<skip>${docker.fabric.skip}</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,47 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.file;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
import org.springframework.context.annotation.ComponentScan;
/**
* Desk启动器
*
* @author Chill
*/
@BladeCloudApplication
@ComponentScan({"org.springblade.file", "org.springblade.**.feign"})
public class FileApplication {
public static void main(String[] args) {
BladeApplication.run(AppConstant.APPLICATION_FILE_NAME, FileApplication.class, args);
}
}

View File

@@ -0,0 +1,33 @@
package org.springblade.file.config;
import com.obs.services.ObsClient;
import org.springblade.common.utils.ObsUtil;
import org.springblade.file.service.impl.ObsFileServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 文件配置类
* @author bfhuange
* @date 2024/10/21
*/
@Import(ObsUtil.class)
@Configuration
public class FileConfig {
@Bean
public ObsProperties obsProperties() {
return new ObsProperties();
}
@Bean
public ObsClient obsClient(ObsProperties properties) {
return new ObsClient(properties.getAccessKeyId(), properties.getAccessKeySecret(), properties.getEndpoint());
}
@Bean
public ObsFileServiceImpl fileService(ObsProperties properties, ObsClient obsClient) {
return new ObsFileServiceImpl(properties, obsClient);
}
}

View File

@@ -0,0 +1,69 @@
package org.springblade.file.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.File;
import java.time.Duration;
/**
* obs 配置
* @author bfhuange
* @date 2024/10/21
*/
@Data
@ConfigurationProperties(prefix = "obs")
public class ObsProperties {
/**
* OBS endpoint
*/
public String endpoint;
/**
* AK in the access key
*/
public String accessKeyId;
/**
* SK in the access key
*/
public String accessKeySecret;
/**
* 桶名
*/
public String bucketName;
/**
* 根目录
*/
public String rootDirectory;
/**
* 过期时间默认1小时
*/
public Long expires = Duration.ofHours(1).toSeconds();
/**
* 本地临时下载文件目录
*/
public String tmpDownloadDir = "tmp" + File.separator + "download";
/**
* 本地临时解压文件目录
*/
public String tmpUnzipDir = "tmp" + File.separator + "unzip";
/**
* obs上压缩文件上传目录
*/
public String zipUploadDir = "part";
/**
* obs上解压后的文件上传目录
*/
public String unzipUploadDir = "unzip";
/**
* obs上设置了过期时间自动删除的上传目录
*/
public String tmpUploadDir = "tmp";
/**
* 下载进度间隔,默认 10m
*/
public Long downloadProgressInterval = 10 * 1024 * 1024L;
/**
* 上传进度间隔,默认 10m
*/
public Long uploadProgressInterval = 10 * 1024 * 1024L;
}

View File

@@ -0,0 +1,75 @@
package org.springblade.file.controller;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.springblade.core.tool.api.R;
import org.springblade.file.pojo.dto.BusinessAttachmentAddDTO;
import org.springblade.file.pojo.dto.BusinessAttachmentBatchAddDTO;
import org.springblade.file.pojo.dto.BusinessAttachmentQueryDTO;
import org.springblade.file.pojo.vo.BusinessAttachmentVO;
import org.springblade.file.service.IBusinessAttachmentService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 业务附件信息表 控制器
*
* @author BladeX
* @since 2024-09-04
*/
@Valid
@RestController
@AllArgsConstructor
@RequestMapping("businessAttachment")
@Tag(name = "业务附件信息表", description = "业务附件信息表接口")
public class BusinessAttachmentController {
private final IBusinessAttachmentService businessAttachmentService;
/**
* 业务附件信息表 新增
*/
@PostMapping("/batchSave")
@ApiOperationSupport(order = 1)
@Operation(summary = "批量新增", description = "批量新增")
public R batchSave(@Validated @RequestBody List<BusinessAttachmentAddDTO> params) {
return R.status(businessAttachmentService.saveAndRemove(params, null));
}
/**
* 业务附件信息表 新增
*/
@PostMapping("/save")
@ApiOperationSupport(order = 2)
@Operation(summary = "新增", description = "新增")
public R save(@Validated @RequestBody BusinessAttachmentBatchAddDTO param) {
return R.status(businessAttachmentService.saveAndRemove(param));
}
/**
* 业务附件信息表 删除
*/
@PostMapping("/remove")
@ApiOperationSupport(order = 3)
@Operation(summary = "逻辑删除", description = "传入bizId")
public R remove(@Parameter(description = "bizId", required = true) @RequestParam Long bizId) {
return R.status(businessAttachmentService.removeByBizIds(List.of(bizId)));
}
/**
* 查询业务附件信息列表
*/
@PostMapping("/queryList")
@ApiOperationSupport(order = 4)
@Operation(summary = "查询业务附件信息列表", description = "查询业务附件信息列表")
public R<List<BusinessAttachmentVO>> queryList(@Validated @RequestBody BusinessAttachmentQueryDTO param) {
return R.data(businessAttachmentService.queryList(param));
}
}

View File

@@ -0,0 +1,69 @@
package org.springblade.file.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springblade.core.log.annotation.ApiLog;
import org.springblade.core.log.utils.AssertUtils;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.api.R;
import org.springblade.file.pojo.dto.BusinessUnzipTaskQueryDTO;
import org.springblade.file.pojo.dto.BusinessUnzipTaskUpdateDTO;
import org.springblade.file.pojo.enums.UnzipTask;
import org.springblade.file.pojo.vo.BusinessUnzipTaskListVO;
import org.springblade.file.service.IBusinessUnzipTaskService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* @author bfhuange
* @date 2024/10/28
*/
@RestController
@AllArgsConstructor
@RequestMapping("/businessUnzipTask")
@Tag(name = "业务文件解压任务", description = "业务文件解压任务")
public class BusinessUnzipTaskController {
private final IBusinessUnzipTaskService unzipTaskService;
/**
* 文件任务列表
* @param param
* @param query
*/
@GetMapping("/list")
@Operation(summary = "业务文件解压任务分页查询", description = "业务文件解压任务分页查询")
@PreAuth("hasPermission('waybill:uploadTask:list')")
public R<IPage<BusinessUnzipTaskListVO>> list(BusinessUnzipTaskQueryDTO param, Query query) {
if (param == null) {
param = new BusinessUnzipTaskQueryDTO();
}
// 只查文件任务类型
param.setRelateType(UnzipTask.RelateType.FILE_TASK.getCode());
if (param.isCurrentUser()) {
// 只查自己的
param.setCurrentUserId(AuthUtil.getUserId());
}
IPage<BusinessUnzipTaskListVO> pages = unzipTaskService.selectPage(Condition.getPage(query), param);
return R.data(pages);
}
/**
* 业务文件解压任务更新(重新上传)
* @param param
* @return
*/
@ApiLog("上传任务-重新上传")
@PostMapping("/update")
@Operation(summary = "业务文件解压任务更新(重新上传)", description = "业务文件解压任务更新(重新上传)")
@PreAuth("hasPermission('waybill:uploadTask:update')")
public R update(@Validated @RequestBody BusinessUnzipTaskUpdateDTO param) {
AssertUtils.notNull(param.getId(), "id不能为空");
return R.status(unzipTaskService.updateReupload(param));
}
}

View File

@@ -0,0 +1,168 @@
package org.springblade.file.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.log.annotation.ApiLog;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.file.listener.FileEvent;
import org.springblade.file.pojo.dto.CertificateAttachmentDTO;
import org.springblade.file.pojo.dto.FileCertificateBatchRecognitionDTO;
import org.springblade.file.pojo.entity.Attachment;
import org.springblade.file.service.IAttachmentService;
import org.springblade.file.service.IFileService;
import org.springblade.file.service.IOCRConvertService;
import org.springblade.thirdparty.ocr.pojo.dto.CertificateRecognitionDTO;
import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO;
import org.springblade.thirdparty.ocr.service.IOCRService;
import org.springblade.thirdparty.wps.service.IWpsService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Optional;
/**
* @ClassName: fileUploadController
* @Author: zhaowei
* @Date: 2024/8/22
*/
@Valid
@NonDS
@RestController
@AllArgsConstructor
@RequestMapping("/file")
@Tag(name = "文件相关管理", description = "文件相关管理")
public class FileController extends BladeController {
private final IAttachmentService attachmentService;
private final IFileService fileService;
private final IWpsService wpsService;
private final IOCRService ocrService;
private final IOCRConvertService ocrConvertService;
private final ApplicationContext applicationContext;
@ApiLog("附件管理-批量上传")
@Operation(summary = "OBS批量上传", description = "OBS批量上传参数名files")
@PostMapping("/ossUpload")
public R ossUpload(MultipartFile[] files, @RequestParam(value = "fileName", required = false) String fileName) {
return attachmentService.batchOssUpload(files, fileName);
}
@Operation(summary = "上传", description = "上传参数名file")
@PostMapping("/upload")
public R upload(MultipartFile file) {
MultipartFile[] files = {file};
R<List<Attachment>> result = attachmentService.batchOssUpload(files, null);
List<Attachment> data = result.getData();
if(data != null && !data.isEmpty())return R.data(data.get(0));
return R.fail("上传失败");
}
@Operation(summary = "获取附件多个附件id用逗号分割", description = "获取附件多个附件id用逗号分割")
@GetMapping("/getAttachment")
public R getAttachment(@RequestParam(value = "id") String id) {
return R.data(attachmentService.getAttachment(id));
}
@Operation(summary = "获取文件url", description = "获取文件url参数:objectKey")
@GetMapping("/getFileUrl")
public R getFileUrl(@RequestParam(value = "objectKey") String objectKey, @RequestParam(value = "attachmentName", required = false) String attachmentName) {
if (StringUtil.isBlank(attachmentName)) {
// 附件名为空,查询附件名
Attachment attachment = attachmentService.getOne(Wrappers.<Attachment>lambdaQuery()
.eq(Attachment::getObjectKey, objectKey)
.last("limit 1")
);
attachmentName = Optional.ofNullable(attachment)
.map(Attachment::getAttachmentName)
.orElse(null);
if (attachmentName != null) {
// 附件名中如果有逗号,则替换为下划线
attachmentName = attachmentName.replaceAll(",", "_");
}
}
return R.data(fileService.getFileUrl(objectKey, attachmentName, null));
}
/**
* 获取wps文件预览url
* @param attachmentId * @param attachmentName
* @return
*/
@Operation(summary = "获取wps文件预览url", description = "获取wps文件预览url")
@GetMapping("/getWpsFilePreviewUrl")
public R getWpsFilePreviewUrl(@NotNull(message = "附件id不能为空") @RequestParam(value = "attachmentId", required = false) String attachmentId,
@NotBlank(message = "附件名称不能为空") @RequestParam(value = "attachmentName", required = false) String attachmentName) {
return R.data(wpsService.getFilePreviewUrl(attachmentId, attachmentName));
}
/**
* 获取wps文件编辑url
* @param attachmentId
* @param attachmentName
* @return
*/
@Operation(summary = "获取wps文件编辑url", description = "获取wps文件编辑url")
@GetMapping("/getWpsFileEditUrl")
public R getWpsFileEditUrl(@NotNull(message = "附件id不能为空") @RequestParam(value = "attachmentId", required = false) String attachmentId,
@NotBlank(message = "附件名称不能为空") @RequestParam(value = "attachmentName", required = false) String attachmentName) {
return R.data(wpsService.getWpsFileEditUrl(attachmentId, attachmentName));
}
@Operation(summary = "批量获取文件url", description = "批量获取文件url参数:objectKey数组")
@PostMapping("/getFileUrls")
public R getFileUrls(@RequestBody List<String> objectKeys) {
return R.data(fileService.getFileUrls(objectKeys, null));
}
@ApiLog("OCR识别-识别身份证")
@Operation(summary = "识别身份证信息支持正反面", description = "参数:objectKey")
@GetMapping("/recognitionIDCard")
public R recognitionIDCard(@NotNull(message = "objectKey不能为空") @RequestParam(value = "objectKey", required = false) String objectKey) {
String fileUrl = fileService.getFileUrl(objectKey, null);
return R.data(ocrService.recognitionIDCard(List.of(fileUrl)));
}
@SentinelResource("ocr:batchCards")
@ApiLog("OCR识别-识别车辆运输凭证(不知道类型)")
@Operation(summary = "识别车辆运输凭证,不知道类型", description = "参数:objectKeylist")
@PostMapping("/recognitionTransportCertificates")
public R<TransportCertificateVO> recognitionTransportCertificates(@RequestBody List<String> objectKeys, @RequestParam(value = "projectAbbreviation", required = false) String projectAbbreviation, @RequestParam(value = "code", required = false) String code) {
return R.data(ocrConvertService.recognitionTransportCertificate(new FileCertificateBatchRecognitionDTO(objectKeys, projectAbbreviation, code, null)));
}
@ApiLog("OCR识别-识别车辆运输凭证(知道类型)")
@Operation(summary = "识别车辆运输凭证,知道类型", description = "参数:objectKeylist")
@PostMapping("/recognitionTransportCertificatesByType")
public R<TransportCertificateVO> recognitionTransportCertificatesByType(@RequestBody CertificateAttachmentDTO param, @RequestParam(value = "code", required = false) String code) {
CertificateRecognitionDTO recognitionParam = new CertificateRecognitionDTO();
recognitionParam.setPoundUrl(fileService.getFileUrl(param.getPoundObjectKey(), null));
recognitionParam.setCarFrontUrl(fileService.getFileUrl(param.getCarFrontObjectKey(), null));
recognitionParam.setDriverLicenseUrl(fileService.getFileUrl(param.getDriverLicenseObjectKey(), null));
recognitionParam.setVehicleLicenseUrl(fileService.getFileUrl(param.getVehicleLicenseObjectKey(), null));
recognitionParam.setName(param.getProjectAbbreviation());
return R.data(ocrService.recognitionTransportCertificate(recognitionParam, code));
}
@Operation(hidden = true)
@PostMapping("/publishEvent")
public R<Boolean> publishEvent(String code, Long id) {
ApplicationEvent event = FileEvent.createEvent(code, id);
if (event == null) {
return R.fail("事件名称错误");
}
applicationContext.publishEvent(event);
return R.status(true);
}
}

View File

@@ -0,0 +1,85 @@
package org.springblade.file.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.log.annotation.ApiLog;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.file.pojo.dto.FileTaskCreateDTO;
import org.springblade.file.pojo.dto.FileTaskUpdateDTO;
import org.springblade.file.pojo.dto.IdsDTO;
import org.springblade.file.pojo.vo.FileTaskUpdateVO;
import org.springblade.file.service.IFileTaskService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* @ClassName: FileTaskController
* @Author: zhaowei
* @Date: 2024/8/28
*/
@Valid
@NonDS
@RestController
@AllArgsConstructor
@RequestMapping("/fileTask")
@Tag(name = "文件任务", description = "文件任务")
public class FileTaskController extends BladeController {
private final IFileTaskService fileTaskService;
@GetMapping("/queryFileTask")
@Operation(summary = "根据文件的MD5值查询文件任务", description = "文件的MD5值检查分段参数名md5")
public R<FileTaskUpdateVO> queryFileTask(@RequestParam(value = "md5") String md5) {
FileTaskUpdateVO fileTask = fileTaskService.queryFileTask(null, md5);
return R.data(fileTask);
}
@ApiLog("上传任务-创建文件任务")
@PostMapping("/createFileTask")
@Operation(summary = "创建文件任务", description = "创建文件任务")
public R<FileTaskUpdateVO> createFileTask(@RequestBody FileTaskCreateDTO param) {
Long id = fileTaskService.createFileTask(param);
return R.data(fileTaskService.queryFileTask(id, null));
}
@ApiLog("上传任务-更新文件任务进度")
@PostMapping("/updateFileTask")
@Operation(summary = "更新文件任务进度", description = "更新文件任务进度")
public R<FileTaskUpdateVO> updateFileTask(@RequestBody FileTaskUpdateDTO param) {
return R.data(fileTaskService.updateFileTask(param));
}
@ApiLog("上传任务-修改文件任务状态为失败")
@PostMapping("/updateFailed")
@Operation(summary = "更新文件任务为失败", description = "更新文件任务为失败")
public R<FileTaskUpdateVO> updateFailed(@NotNull(message = "文件任务id不能为空") Long id) {
return R.data(fileTaskService.updateFailed(id));
}
@ApiLog("上传任务-暂停文件任务")
@PostMapping("/updatePaused")
@Operation(summary = "更新文件任务为暂停", description = "更新文件任务为暂停")
public R<Boolean> updatePaused(@Validated @RequestBody IdsDTO param) {
return R.status(fileTaskService.updatePaused(param.getIds()));
}
@GetMapping("/getUploadUrl")
@Parameters({
@Parameter(name = "id", description = "文件任务id", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
@Parameter(name = "partNumber", description = "分段编号", in = ParameterIn.QUERY, schema = @Schema(type = "int"))
})
@Operation(summary = "根据文件任务id查询上传链接", description = "根据文件任务id查询上传链接")
public R<String> getUploadUrl(Long id, Integer partNumber) {
String uploadUrl = fileTaskService.getUploadUrl(id, partNumber);
return R.data(uploadUrl);
}
}

View File

@@ -0,0 +1,44 @@
package org.springblade.file.convert;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ReportingPolicy;
import org.springblade.file.pojo.dto.BusinessAttachmentAddDTO;
import org.springblade.file.pojo.dto.BusinessAttachmentBatchAddDTO;
import org.springblade.file.pojo.entity.Attachment;
import org.springblade.file.pojo.entity.BusinessAttachment;
import org.springblade.file.pojo.vo.AttachmentDetailVO;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author bfhuange
* @date 2024/9/4
*/
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface BusinessAttachmentConvert {
BusinessAttachment dto2entity(BusinessAttachmentAddDTO dto);
List<BusinessAttachment> dtos2entities(List<BusinessAttachmentAddDTO> dtos);
AttachmentDetailVO entity2vo(Attachment entity);
default List<BusinessAttachment> dto2entities(BusinessAttachmentBatchAddDTO dto) {
if (dto == null || dto.getDetails() == null) {
return Collections.emptyList();
}
AtomicInteger sort = new AtomicInteger(1);
return dto.getDetails().stream()
.flatMap(detail -> detail.getAttachmentIds().stream().map(attachmentId -> {
BusinessAttachment businessAttachment = new BusinessAttachment();
businessAttachment.setAttachmentId(attachmentId);
businessAttachment.setBizId(dto.getBizId());
businessAttachment.setBizCode(detail.getBizCode());
businessAttachment.setSort(sort.getAndIncrement());
return businessAttachment;
})).toList();
}
}

View File

@@ -0,0 +1,31 @@
package org.springblade.file.convert;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ReportingPolicy;
import org.springblade.file.pojo.dto.BusinessUnzipTaskAddDTO;
import org.springblade.file.pojo.dto.FileTaskCreateDTO;
import org.springblade.file.pojo.entity.BusinessUnzipTask;
import org.springblade.file.pojo.entity.FileTask;
import org.springblade.file.pojo.entity.FileTaskPart;
import org.springblade.file.pojo.vo.FileTaskUpdateVO;
import org.springblade.file.pojo.vo.FileTaskVO;
import org.springblade.file.pojo.vo.PartEtagVO;
/**
* @author bfhuange
* @date 2024/10/22
*/
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface FileTaskConvert {
FileTaskVO entity2vo(FileTask entity);
FileTaskUpdateVO entity2updateVo(FileTask entity);
FileTask dto2entity(FileTaskCreateDTO dto);
PartEtagVO entity2vo(FileTaskPart entity);
BusinessUnzipTask dto2entity(BusinessUnzipTaskAddDTO dto);
}

View File

@@ -0,0 +1,17 @@
package org.springblade.file.convert;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ReportingPolicy;
import org.springblade.file.pojo.dto.FileCertificateBatchRecognitionDTO;
import org.springblade.thirdparty.ocr.pojo.dto.CertificateBatchRecognitionDTO;
/**
* @author bfhuange
* @since 2025/3/13
*/
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface OCRConverter {
CertificateBatchRecognitionDTO dto2ocr(FileCertificateBatchRecognitionDTO dto);
}

View File

@@ -0,0 +1,28 @@
package org.springblade.file.exception;
import lombok.Getter;
import java.io.File;
/**
* 文件解压失败异常
* @author bfhuange
* @date 2024/11/11
*/
@Getter
public class UnzipFailException extends RuntimeException{
/**
* 解压目录
*/
private final File unzipDir;
public UnzipFailException(Throwable cause, File unzipDir) {
super(cause);
this.unzipDir = unzipDir;
}
public UnzipFailException(String message, File unzipDir) {
super(message);
this.unzipDir = unzipDir;
}
}

View File

@@ -0,0 +1,157 @@
package org.springblade.file.feign;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.utils.AssertUtils;
import org.springblade.core.tool.api.FR;
import org.springblade.core.tool.api.R;
import org.springblade.file.config.ObsProperties;
import org.springblade.file.pojo.dto.*;
import org.springblade.file.pojo.entity.Attachment;
import org.springblade.file.pojo.vo.*;
import org.springblade.file.service.*;
import org.springblade.file.util.ObjectKeyUtils;
import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* @author bfhuange
* @date 2024/10/21
*/
@Slf4j
@Valid
@Hidden
@RestController
@AllArgsConstructor
public class FileClient implements IFileClient, IFileUploadClient {
private final IAttachmentService attachmentService;
private final IBusinessAttachmentService businessAttachmentService;
private final IBusinessUnzipTaskService businessUnzipTaskService;
private final IOCRConvertService ocrConvertService;
private final IFileService fileService;
private final ObsProperties obsProperties;
@Override
@GetMapping(ATTACHMENT)
public FR<List<Attachment>> getAttachment(String id) {
return FR.data(attachmentService.getAttachment(id));
}
@Override
public FR<AttachmentDetailVO> getAttachmentDetail(Long id) {
return FR.data(attachmentService.getDetail(id));
}
@Override
public FR saveAndRemoveBusinessAttachment(@RequestBody @Validated BusinessAttachmentBatchAddDTO param) {
return FR.status(businessAttachmentService.saveAndRemove(param));
}
@Override
public FR batchSaveAndRemoveBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params, @RequestParam(value = "bizId", required = false) Long bizId) {
return FR.status(businessAttachmentService.saveAndRemove(params, new BusinessAttachmentDeleteDTO(bizId)));
}
@Override
public FR batchSaveAndRemoveByParamBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params, @Valid BusinessAttachmentDeleteDTO deleteParam) {
return FR.status(businessAttachmentService.saveAndRemove(params, deleteParam));
}
@Override
public FR batchSaveAndRemoveBusinessAttachment(List<@Valid BusinessAttachmentAddDTO> params) {
return FR.status(businessAttachmentService.saveAndRemove(params, null));
}
@Override
public FR removeBusinessAttachment(@NotNull(message = "业务id不能为空") @RequestParam(value = "bizId", required = false) Long bizId) {
businessAttachmentService.removeByBizIds(List.of(bizId));
return FR.data(true);
}
@Override
public FR<List<BusinessAttachmentVO>> getBusinessAttachments(@Validated @RequestBody BusinessAttachmentQueryDTO param) {
return FR.data(businessAttachmentService.queryList(param));
}
@Override
public FR saveBusinessUnzipTask(BusinessUnzipTaskAddDTO param) {
return FR.status(businessUnzipTaskService.save(param));
}
@Override
public FR updateBusinessUnzipTask(BusinessUnzipTaskUpdateDTO param) {
AssertUtils.notNull(param.getBizId(), "业务id不能为空");
return FR.status(businessUnzipTaskService.updateReupload(param));
}
@Override
public FR cancelBusinessUnzipTask(@NotNull(message = "业务id不能为空") Long bizId) {
return FR.status(businessUnzipTaskService.cancelBusinessUnzipTask(bizId, true));
}
@Override
public FR removeBusinessUnzipTask(Long bizId) {
return FR.status(businessUnzipTaskService.cancelBusinessUnzipTask(bizId, false));
}
@Override
public FR<List<BusinessUnzipTaskListVO>> getBusinessUnzipTasksByBizIds(List<Long> bizIds) {
return FR.data(businessUnzipTaskService.getBusinessUnzipTasksByBizIds(bizIds));
}
@Override
public FR<List<BusinessUnzipDetailListVO>> queryBusinessUnzipDetail(@NotNull(message = "业务id不能为空") Long bizId) {
return FR.data(businessUnzipTaskService.queryBusinessUnzipDetail(bizId));
}
@SentinelResource("ocr:batchCards")
@Override
public FR<TransportCertificateVO> recognitionTransportCertificates(@Validated @RequestBody FileCertificateBatchRecognitionDTO param) {
return FR.data(ocrConvertService.recognitionTransportCertificate(param));
}
@Override
public FR<List<FileUrlVO>> getFileUrls(IdsDTO param) {
List<FileUrlVO> list = attachmentService.listByIds(param.getIds()).stream()
.map(attachment -> {
String fileUrl = fileService.getFileUrl(attachment.getObjectKey(), attachment.getAttachmentName(), null);
return new FileUrlVO(attachment.getId(), fileUrl);
})
.toList();
return FR.data(list);
}
@Override
public FR<List<Attachment>> ossUpload(MultipartFile[] files) {
R<List<Attachment>> r = attachmentService.batchOssUpload(files, null);
return R.isNotSuccess(r) ? FR.fail(r.getCode(), r.getMsg()) : FR.data(r.getData(), r.getMsg());
}
@Override
public FR<TempFileUrlVO> ossUploadTemp(MultipartFile file, Integer expires) {
if (expires == null) {
// 默认1天过期
expires = 1;
}
try {
String objectKey = ObjectKeyUtils.generateObjectKey(obsProperties.getTmpUploadDir(), file.getOriginalFilename());
String fileUrl = fileService.uploadTempFile(objectKey, file.getInputStream(), expires);
return FR.data(new TempFileUrlVO(objectKey, fileUrl));
} catch (Exception e) {
log.error("上传临时文件异常", e);
return FR.fail("上传临时文件异常");
}
}
}

View File

@@ -0,0 +1,56 @@
package org.springblade.file.listener;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* 文件事件枚举
* @author bfhuange
* @since 2024/11/22
*/
@AllArgsConstructor
@Getter
public enum FileEvent {
/**
* 上传完成事件
*/
UPLOADED(FileUploadedEvent::new),
/**
* 解压开始事件
*/
UNZIP_START(UnzipStartEvent::new),
/**
* 解压完成事件
*/
UNZIP_COMPLETED(UnzipCompletedEvent::new),
/**
* 解压失败事件
*/
UNZIP_FAILED(UnzipFailEvent::new),
;
/**
* 事件函数
*/
private final Function<Long, ApplicationEvent> eventFunction;
/**
* 根据编码创建事件
* @param code
* @param id
* @return
*/
public static ApplicationEvent createEvent(String code, Long id) {
if (code == null || code.isBlank()) {
return null;
}
return Stream.of(values())
.filter(event -> event.name().equalsIgnoreCase(code))
.findFirst()
.map(event -> event.getEventFunction().apply(id))
.orElse(null);
}
}

View File

@@ -0,0 +1,173 @@
package org.springblade.file.listener;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.log.utils.AssertUtils;
import org.springblade.file.callback.UnzipCallback;
import org.springblade.file.callback.UnzipEventType;
import org.springblade.file.pojo.entity.BusinessUnzipTask;
import org.springblade.file.pojo.enums.UnzipTask;
import org.springblade.file.pojo.vo.FileVO;
import org.springblade.file.service.IBusinessUnzipDetailService;
import org.springblade.file.service.IBusinessUnzipTaskService;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 文件监听器
* @author bfhuange
* @date 2024/11/8
*/
@Slf4j
@Component
public class FileListener {
private final IBusinessUnzipTaskService taskService;
private final IBusinessUnzipDetailService detailService;
private final Map<String, UnzipCallback> callbackMap;
public FileListener(IBusinessUnzipTaskService taskService, IBusinessUnzipDetailService detailService, List<UnzipCallback> callbacks) {
this.taskService = taskService;
this.detailService = detailService;
this.callbackMap = callbacks.stream()
.collect(Collectors.toMap(UnzipCallback::getBizCode, Function.identity(), (a, b) -> {
throw new ServiceException("重复的解压完成回调接口");
}));
}
/**
* 文件上传完成监听器
* @param event
*/
@Async
@TransactionalEventListener(value = FileUploadedEvent.class, phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
public void fileUploaded(FileUploadedEvent event) {
try {
Long fileTaskId = event.getFileTaskId();
log.info("文件上传任务完成事件 id{}", fileTaskId);
BusinessUnzipTask businessUnzipTask = taskService.getOne(Wrappers.<BusinessUnzipTask>lambdaQuery()
.eq(BusinessUnzipTask::getRelateId, fileTaskId)
.last("limit 1")
);
if (businessUnzipTask != null) {
// 存在解压任务,更新任务为上传完成
BusinessUnzipTask updateParam = new BusinessUnzipTask();
updateParam.setId(businessUnzipTask.getId());
updateParam.setStatus(UnzipTask.Status.UPLOADED.getCode());
taskService.updateById(updateParam);
// 解压开始
this.unzipStart(new UnzipStartEvent(businessUnzipTask));
}
} catch (Exception e) {
log.error("文件上传任务完成事件 异常", e);
}
}
/**
* 解压开始
* @param event
*/
@Async
@TransactionalEventListener(value = UnzipStartEvent.class, phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
public void unzipStart(UnzipStartEvent event) {
try {
Long unzipTaskId = event.getUnzipTaskId();
BusinessUnzipTask task = event.getTask();
log.info("解压开始事件 id{} 业务解压任务:{}", unzipTaskId, JSON.toJSONString(task));
if (task == null) {
// 手动触发时只有id
task = taskService.getById(unzipTaskId);
}
// 回调接口
callback(task, UnzipEventType.START);
// 解压并保存
detailService.unzipAndSaveAsync(task.getId(), taskService.getFile(task));
} catch (Exception e) {
log.error("解压开始事件 异常", e);
}
}
/**
* 解压完成监听器
* @param event
*/
@Async
@EventListener(UnzipCompletedEvent.class)
public void unzipCompleted(UnzipCompletedEvent event) {
try {
Long unzipTaskId = event.getUnzipTaskId();
log.info("解压完成事件 id{}", unzipTaskId);
// 查询解压任务
BusinessUnzipTask unzipTask = taskService.getById(unzipTaskId);
AssertUtils.notNull(unzipTask, "解压任务不存在");
// 更新解压任务状态
BusinessUnzipTask updateParam = new BusinessUnzipTask();
updateParam.setId(unzipTaskId);
updateParam.setStatus(UnzipTask.Status.FINISHED.getCode());
taskService.updateById(updateParam);
// 回调接口
callback(unzipTask, UnzipEventType.SUCCESS);
} catch (Exception e) {
log.error("解压完成事件 异常", e);
}
}
/**
* 解压失败监听器
* @param event
*/
@Async
@EventListener(UnzipFailEvent.class)
public void unzipFail(UnzipFailEvent event) {
try {
Long unzipTaskId = event.getUnzipTaskId();
log.info("解压失败事件 id{}", unzipTaskId);
// 查询解压任务
BusinessUnzipTask unzipTask = taskService.getById(unzipTaskId);
AssertUtils.notNull(unzipTask, "解压任务不存在");
// 更新解压任务状态
BusinessUnzipTask updateParam = new BusinessUnzipTask();
updateParam.setId(unzipTaskId);
updateParam.setStatus(UnzipTask.Status.UNZIP_FAIL.getCode());
taskService.updateById(updateParam);
// 回调接口
callback(unzipTask, UnzipEventType.FAIL);
} catch (Exception e) {
log.error("解压失败事件 异常", e);
}
}
/**
* 回调接口
* @param unzipTask
* @param event
*/
private void callback(BusinessUnzipTask unzipTask, UnzipEventType event) {
if (unzipTask == null) {
log.warn("解压回调接口 解压任务为空");
return;
}
UnzipCallback unzipCallback = callbackMap.get(unzipTask.getBizCode());
if (unzipCallback == null) {
log.error("未配置业务编码:{}对应的解压回调接口", unzipTask.getBizCode());
return;
}
Boolean result = null;
try {
result = unzipCallback.callback(unzipTask.getBizId(), event.name());
} catch (Exception e) {
log.error("解压回调接口异常", e);
}
log.info("解压回调接口参数:{} 业务编码:{} 事件:{} 结果:{}", unzipTask.getBizId(), unzipTask.getBizCode(), event.name(), result);
}
}

View File

@@ -0,0 +1,21 @@
package org.springblade.file.listener;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
/**
* @author bfhuange
* @date 2024/11/8
*/
@Getter
public class FileUploadedEvent extends ApplicationEvent {
/**
* 文件任务id
*/
private final Long fileTaskId;
public FileUploadedEvent(Long fileTaskId) {
super(fileTaskId);
this.fileTaskId = fileTaskId;
}
}

View File

@@ -0,0 +1,22 @@
package org.springblade.file.listener;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
/**
* 解压完成事件
* @author bfhuange
* @date 2024/11/8
*/
@Getter
public class UnzipCompletedEvent extends ApplicationEvent {
/**
* 解压任务id
*/
private final Long unzipTaskId;
public UnzipCompletedEvent(Long unzipTaskId) {
super(unzipTaskId);
this.unzipTaskId = unzipTaskId;
}
}

View File

@@ -0,0 +1,22 @@
package org.springblade.file.listener;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
/**
* 解压失败事件
* @author bfhuange
* @date 2024/11/8
*/
@Getter
public class UnzipFailEvent extends ApplicationEvent {
/**
* 解压任务id
*/
private final Long unzipTaskId;
public UnzipFailEvent(Long unzipTaskId) {
super(unzipTaskId);
this.unzipTaskId = unzipTaskId;
}
}

View File

@@ -0,0 +1,35 @@
package org.springblade.file.listener;
import lombok.Getter;
import org.springblade.file.pojo.entity.BusinessUnzipTask;
import org.springblade.file.pojo.vo.FileVO;
import org.springframework.context.ApplicationEvent;
/**
* 解压开始事件
* @author bfhuange
* @date 2024/11/8
*/
@Getter
public class UnzipStartEvent extends ApplicationEvent {
/**
* 解压任务id
*/
private final Long unzipTaskId;
/**
* 解压任务
*/
private final BusinessUnzipTask task;
public UnzipStartEvent(Long unzipTaskId) {
super(unzipTaskId);
this.unzipTaskId = unzipTaskId;
this.task = null;
}
public UnzipStartEvent(BusinessUnzipTask task) {
super(task);
this.task = task;
this.unzipTaskId = null;
}
}

View File

@@ -0,0 +1,37 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.file.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.file.pojo.entity.Attachment;
/**
* AttachmentMapper 接口
*
* @author zhaowei
*/
public interface AttachmentMapper extends BaseMapper<Attachment> {
}

View File

@@ -0,0 +1,5 @@
<?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="org.springblade.file.mapper.AttachmentMapper">
</mapper>

View File

@@ -0,0 +1,24 @@
package org.springblade.file.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.file.pojo.dto.BusinessAttachmentQueryDTO;
import org.springblade.file.pojo.entity.BusinessAttachment;
import org.springblade.file.pojo.vo.BusinessAttachmentVO;
import java.util.List;
/**
* 业务附件信息表 Mapper 接口
*
* @author BladeX
* @since 2024-09-04
*/
public interface BusinessAttachmentMapper extends BaseMapper<BusinessAttachment> {
/**
* 查询业务附件列表
* @param param
* @return
*/
List<BusinessAttachmentVO> queryList(BusinessAttachmentQueryDTO param);
}

View File

@@ -0,0 +1,48 @@
<?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="org.springblade.file.mapper.BusinessAttachmentMapper">
<!-- 通用查询映射结果 -->
<resultMap id="businessAttachmentResultMap" type="org.springblade.file.pojo.entity.BusinessAttachment">
<result column="id" property="id"/>
<result column="tenant_id" property="tenantId"/>
<result column="biz_id" property="bizId"/>
<result column="biz_code" property="bizCode"/>
<result column="attachment_id" property="attachmentId"/>
<result column="`desc`" property="desc"/>
<result column="sort" property="sort"/>
<result column="create_user" property="createUser"/>
<result column="create_time" property="createTime"/>
<result column="update_user" property="updateUser"/>
<result column="update_time" property="updateTime"/>
<result column="is_deleted" property="isDeleted"/>
</resultMap>
<select id="queryList" resultType="org.springblade.file.pojo.vo.BusinessAttachmentVO">
select
bba.id,
bba.biz_id,
bba.biz_code,
bba.attachment_id,
bba.`desc`,
bba.sort,
ba.dir,
ba.attachment_name,
ba.size,
ba.object_key,
ba.create_user uploadUserId,
ba.create_user_name uploadUserName,
ba.create_time,
ba.create_user_name
from blade_business_attachment bba
left join blade_attachment ba on ba.id = bba.attachment_id
where bba.is_deleted = 0 and ba.is_deleted = 0 and bba.biz_id in <foreach collection="bizIds" item="item" open="(" close=")" separator=",">#{item}</foreach>
<if test="bizCode != null and bizCode != ''">
and bba.biz_code = #{bizCode}
</if>
<if test="bizCodes != null and bizCodes.size() > 0">
and bba.biz_code in <foreach collection="bizCodes" item="item" open="(" close=")" separator=",">#{item}</foreach>
</if>
order by bba.biz_id, bba.sort, bba.id
</select>
</mapper>

View File

@@ -0,0 +1,16 @@
package org.springblade.file.mapper;
import org.springblade.file.pojo.entity.BusinessUnzipDetail;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 业务解压明细表 Mapper 接口
* </p>
*
* @author bfhuange
* @since 2024-10-30
*/
public interface BusinessUnzipDetailMapper extends BaseMapper<BusinessUnzipDetail> {
}

View File

@@ -0,0 +1,22 @@
<?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="org.springblade.file.mapper.BusinessUnzipDetailMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="org.springblade.file.pojo.entity.BusinessUnzipDetail">
<id column="id" property="id" />
<result column="business_unzip_task_id" property="businessUnzipTaskId" />
<result column="attachment_id" property="attachmentId" />
<result column="create_user" property="createUser" />
<result column="create_time" property="createTime" />
<result column="update_user" property="updateUser" />
<result column="update_time" property="updateTime" />
<result column="is_deleted" property="isDeleted" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, business_unzip_task_id, attachment_id, create_user, create_time, update_user, update_time, is_deleted
</sql>
</mapper>

View File

@@ -0,0 +1,37 @@
package org.springblade.file.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Param;
import org.springblade.file.pojo.dto.BusinessUnzipTaskQueryDTO;
import org.springblade.file.pojo.entity.BusinessUnzipTask;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.file.pojo.vo.BusinessUnzipDetailListVO;
import org.springblade.file.pojo.vo.BusinessUnzipTaskListVO;
import java.util.List;
/**
* <p>
* 业务解压任务表 Mapper 接口
* </p>
*
* @author bfhuange
* @since 2024-10-28
*/
public interface BusinessUnzipTaskMapper extends BaseMapper<BusinessUnzipTask> {
/**
* 查询列表
* @param page
* @param param
* @return
*/
List<BusinessUnzipTaskListVO> selectTaskList(IPage<BusinessUnzipTask> page, @Param("param") BusinessUnzipTaskQueryDTO param);
/**
* 根据业务id查询解压明细
* @param bizId
* @return
*/
List<BusinessUnzipDetailListVO> queryBusinessUnzipDetail(@Param("bizId") Long bizId);
}

View File

@@ -0,0 +1,95 @@
<?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="org.springblade.file.mapper.BusinessUnzipTaskMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="org.springblade.file.pojo.entity.BusinessUnzipTask">
<id column="id" property="id" />
<result column="biz_id" property="bizId" />
<result column="department_id" property="departmentId" />
<result column="biz_code" property="bizCode" />
<result column="doc_code" property="docCode" />
<result column="relate_type" property="relateType" />
<result column="relate_id" property="relateId" />
<result column="status" property="status" />
<result column="tenant_id" property="tenantId" />
<result column="create_user" property="createUser" />
<result column="create_time" property="createTime" />
<result column="update_user" property="updateUser" />
<result column="update_time" property="updateTime" />
<result column="is_deleted" property="isDeleted" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, biz_id, department_id, biz_code, doc_code, relate_type, relate_id, status, tenant_id, create_user, create_time, update_user, update_time, is_deleted
</sql>
<select id="selectTaskList" resultType="org.springblade.file.pojo.vo.BusinessUnzipTaskListVO">
select
but.id,
but.biz_id,
but.biz_code,
but.doc_code,
but.relate_type,
but.relate_id,
but.department_id,
but.create_user b_create_user,
ft.md5,
ft.object_key,
ft.attachment_name,
ft.suffix,
ft.size,
ft.chunk_size,
ft.chunk_total,
ft.current_index,
ft.upload_id,
ft.status,
ft.create_user,
ft.create_user_name,
ft.create_time,
ft.update_user,
ft.update_user_name,
ft.update_time
from blade_business_unzip_task but
left join blade_file_task ft on ft.id = but.relate_id
where but.is_deleted = 0 and ft.is_deleted = 0
<if test="param.bizIds != null and param.bizIds.size() > 0">
and but.biz_id in <foreach collection="param.bizIds" item="item" open="(" close=")" separator=",">#{item}</foreach>
</if>
<if test="param.docCode != null and param.docCode != ''">
and but.doc_code like concat('%',#{param.docCode},'%')
</if>
<if test="param.relateType != null and param.relateType != ''">
and but.relate_type = #{param.relateType}
</if>
<if test="param.currentUserId != null">
and but.create_user = #{param.currentUserId}
</if>
<if test="param.attachmentName != null and param.attachmentName != ''">
and ft.attachment_name like concat('%',#{param.attachmentName},'%')
</if>
<if test="param.status != null and param.status != ''">
and ft.status = #{param.status}
</if>
order by Field(ft.status, 'uploading','failed', 'completed'), but.id desc
</select>
<select id="queryBusinessUnzipDetail" resultType="org.springblade.file.pojo.vo.BusinessUnzipDetailListVO">
select
bbud.id,
bbud.attachment_id,
ba.dir,
ba.attachment_name,
ba.size,
ba.object_key,
ba.create_user uploadUserId,
ba.create_user_name uploadUserName,
ba.create_time,
ba.create_user_name
from blade_business_unzip_task bbut
left join blade_business_unzip_detail bbud on bbud.business_unzip_task_id = bbut.id
left join blade_attachment ba on bbud.attachment_id = ba.id
where bbut.is_deleted = 0 and bbud.is_deleted = 0 and ba.is_deleted = 0
and bbut.biz_id = #{bizId}
</select>
</mapper>

View File

@@ -0,0 +1,38 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.file.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.file.pojo.entity.FileTask;
/**
* FileTaskMapper 接口
*
* @author zhaowei
*/
public interface FileTaskMapper extends BaseMapper<FileTask> {
}

View File

@@ -0,0 +1,5 @@
<?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="org.springblade.file.mapper.FileTaskMapper">
</mapper>

View File

@@ -0,0 +1,38 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.file.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.file.pojo.entity.FileTaskPart;
/**
* FileUploadRecordMapper 接口
*
* @author zhaowei
*/
public interface FileTaskPartMapper extends BaseMapper<FileTaskPart> {
}

View File

@@ -0,0 +1,5 @@
<?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="org.springblade.file.mapper.FileTaskPartMapper">
</mapper>

View File

@@ -0,0 +1,89 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.file.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.core.tool.api.R;
import org.springblade.file.pojo.entity.Attachment;
import org.springblade.file.pojo.vo.AttachmentDetailVO;
import org.springblade.file.pojo.vo.FileVO;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.List;
/**
* 服务类
*
* @author zhaowei
*/
public interface IAttachmentService extends IService<Attachment> {
/**
* 批量上传
*
* @param files
* @param fileName 文件名,如果 files只有1个且fileName不为空设置文件名为 fileName
* @return
*/
R<List<Attachment>> batchOssUpload(MultipartFile[] files, String fileName);
/**
* 获取附件多个附件id用逗号分割
* @param id
* @return List<Attachment>
*/
List<Attachment> getAttachment(String id);
/**
* 批量上传解压缩文件并删除文件
*
* @param files
* @param workDir 工作目录,取文件相对于工作目录的相对路径作为前缀
* @return
*/
List<Attachment> batchOssUploadUnzipFilesAndDelete(List<File> files, File workDir);
/**
* 新事务批量保存
* @param list
*/
void batchSaveWithNewTransaction(List<Attachment> list);
/**
* 获取附件详情
* @param id
* @return
*/
AttachmentDetailVO getDetail(Long id);
/**
* 解压文件并上传
* @param file
* @return
*/
List<Attachment> unzipAndUpload(FileVO file);
}

View File

@@ -0,0 +1,51 @@
package org.springblade.file.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.file.pojo.dto.BusinessAttachmentAddDTO;
import org.springblade.file.pojo.dto.BusinessAttachmentBatchAddDTO;
import org.springblade.file.pojo.dto.BusinessAttachmentDeleteDTO;
import org.springblade.file.pojo.dto.BusinessAttachmentQueryDTO;
import org.springblade.file.pojo.entity.BusinessAttachment;
import org.springblade.file.pojo.vo.BusinessAttachmentVO;
import java.util.List;
/**
* 业务附件信息表 服务类
*
* @author BladeX
* @since 2024-09-04
*/
public interface IBusinessAttachmentService extends IService<BusinessAttachment> {
/**
* 保存
* @param param
* @return
*/
boolean saveAndRemove(BusinessAttachmentBatchAddDTO param);
/**
* 保存并删除
*
* @param params
* @param deleteParam 业务id不为空则先删除
* @return
*/
boolean saveAndRemove(List<BusinessAttachmentAddDTO> params, BusinessAttachmentDeleteDTO deleteParam);
/**
* 根据bizId列表删除
*
* @param bizIds
* @return
*/
boolean removeByBizIds(List<Long> bizIds);
/**
* 查询业务附件列表
*
* @param param
* @return
*/
List<BusinessAttachmentVO> queryList(BusinessAttachmentQueryDTO param);
}

View File

@@ -0,0 +1,23 @@
package org.springblade.file.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.file.pojo.entity.BusinessUnzipDetail;
import org.springblade.file.pojo.vo.FileVO;
/**
* <p>
* 业务解压明细表 服务类
* </p>
*
* @author bfhuange
* @since 2024-10-30
*/
public interface IBusinessUnzipDetailService extends IService<BusinessUnzipDetail> {
/**
* 异步解压并保存解压明细
* @param unzipTaskId 解压任务id
* @param file obs对象存储的key合md5值md5有可能为空
*/
void unzipAndSaveAsync(Long unzipTaskId, FileVO file);
}

View File

@@ -0,0 +1,82 @@
package org.springblade.file.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.file.pojo.dto.BusinessUnzipTaskAddDTO;
import org.springblade.file.pojo.dto.BusinessUnzipTaskQueryDTO;
import org.springblade.file.pojo.dto.BusinessUnzipTaskUpdateDTO;
import org.springblade.file.pojo.entity.BusinessUnzipTask;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.file.pojo.vo.BusinessUnzipDetailListVO;
import org.springblade.file.pojo.vo.BusinessUnzipTaskListVO;
import org.springblade.file.pojo.vo.FileVO;
import java.util.List;
/**
* <p>
* 业务解压任务表 服务类
* </p>
*
* @author bfhuange
* @since 2024-10-28
*/
public interface IBusinessUnzipTaskService extends IService<BusinessUnzipTask> {
/**
* 保存业务解压任务
* @param param
* @return
*/
boolean save(BusinessUnzipTaskAddDTO param);
/**
* 获取obs对象存储的key
* @param task
* @return
*/
String getObjectKey(BusinessUnzipTask task);
/**
* 获取文件对象
* @param task
* @return
*/
FileVO getFile(BusinessUnzipTask task);
/**
* 业务解压任务分页查询
* @param page
* @param param
* @return
*/
IPage<BusinessUnzipTaskListVO> selectPage(IPage<BusinessUnzipTask> page, BusinessUnzipTaskQueryDTO param);
/**
* 业务文件解压任务更新(重新上传)
* @param param
* @return
*/
boolean updateReupload(BusinessUnzipTaskUpdateDTO param);
/**
* 根据业务id删除业务文件解压任务
* @param bizId
* @param cancelFile 是否取消文件,如果取消,上传完成后取消会报错
* @return
*/
boolean cancelBusinessUnzipTask(Long bizId, boolean cancelFile);
/**
* 根据业务id查询解压明细
* @param bizId
* @return
*/
List<BusinessUnzipDetailListVO> queryBusinessUnzipDetail(Long bizId);
/**
* 根据业务id列表查询解压任务列表
* @param bizIds
* @return
*/
List<BusinessUnzipTaskListVO> getBusinessUnzipTasksByBizIds(List<Long> bizIds);
}

View File

@@ -0,0 +1,20 @@
package org.springblade.file.service;
import org.springblade.file.pojo.vo.FileHandleVO;
import org.springblade.file.pojo.vo.FileVO;
/**
* 文件处理
* @author bfhuange
* @date 2024/10/22
*/
public interface IFileHandleService {
/**
* 下载文件并解压
*
* @param file obs对象存储的key合md5md5可能为空
* @return
*/
FileHandleVO downloadAndUnzip(FileVO file);
}

View File

@@ -0,0 +1,122 @@
package org.springblade.file.service;
import org.springblade.file.pojo.vo.PartEtagVO;
import java.io.File;
import java.io.InputStream;
import java.util.List;
/**
* 文件接口
* @author bfhuange
* @date 2024/10/21
*/
public interface IFileService {
/**
* 上传文件
* @param objectKey 文件key
* @param is 输入流
*/
void uploadFile(String objectKey, InputStream is);
/**
* 上传临时文件
* @param objectKey 文件key
* @param is 输入流
* @param expires 过期时间,单位天,过期后自动删除
*/
String uploadTempFile(String objectKey, InputStream is, Integer expires);
/**
* 上传文件
* @param objectKey 文件key
* @param file 文件
*/
void uploadFile(String objectKey, File file);
/**
* 初始化分片上传
*
* @param objectKey 文件key
* @return 上传任务id
*/
String initiateMultipartUpload(String objectKey);
/**
* 分段上传
* @param objectKey 文件key
* @param uploadId 上传任务id
* @param file 文件
* @param partNumber 分段编号
* @param offset 偏移位
* @param partSize 分段大小
* @return
*/
PartEtagVO uploadPart(String objectKey, String uploadId, File file, int partNumber, Long offset, Long partSize);
/**
* 完成分段上传任务
* @param objectKey 文件key
* @param uploadId 上传任务id
* @param partEtagVOS 要合并的分段
*/
void completeMultipartUpload(String objectKey, String uploadId, List<PartEtagVO> partEtagVOS);
/**
* 取消分段上传任务
* @param objectKey
* @param uploadId
*/
void cancelMultipartUpload(String objectKey, String uploadId);
/**
* 获取文件链接
* @param objectKey 文件key
* @param expires 过期时间,单位秒
* @return
*/
String getFileUrl(String objectKey, Long expires);
/**
* 获取文件链接
* @param objectKey 文件key
* @param fileName 文件名
* @param expires 过期时间,单位秒
* @return
*/
String getFileUrl(String objectKey, String fileName, Long expires);
/**
* 获取文件上传链接
*
* @param objectKey 文件key
* @param uploadId
* @param partNumber
* @param expires 过期时间,单位秒
* @return
*/
String getUploadPartUrl(String objectKey, String uploadId, Integer partNumber, Long expires);
/**
* 获取文件链接批量
* @param objectKeys
* @param expires 过期时间
* @return
*/
List<String> getFileUrls(List<String> objectKeys, Long expires);
/**
* 下载文件
* @param objectKey
* @param downloadFile 下载路径
*/
void downloadFile(String objectKey, String downloadFile);
/**
* 获取文件md5
* @param file
* @return
*/
String getFileMd5(File file);
}

View File

@@ -0,0 +1,38 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.file.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.file.pojo.entity.FileTaskPart;
/**
* 服务类
*
* @author zhaowei
*/
public interface IFileTaskPartService extends IService<FileTaskPart> {
}

View File

@@ -0,0 +1,94 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.file.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.file.pojo.dto.FileTaskCreateDTO;
import org.springblade.file.pojo.dto.FileTaskUpdateDTO;
import org.springblade.file.pojo.entity.FileTask;
import org.springblade.file.pojo.vo.FileTaskUpdateVO;
import java.util.List;
/**
* 服务类
*
* @author zhaowei
*/
public interface IFileTaskService extends IService<FileTask> {
/**
* 根据文件md5值查询最新一条上传任务记录
*
* @param id
* @param md5
* @return
*/
FileTaskUpdateVO queryFileTask(Long id, String md5);
/**
* 创建文件任务
* @param param
* @return
*/
Long createFileTask(FileTaskCreateDTO param);
/**
* 更新文件任务进度
* @param param
* @return
*/
FileTaskUpdateVO updateFileTask(FileTaskUpdateDTO param);
/**
* 根据文件任务id获取上传链接
*
* @param id
* @param partNumber
* @return
*/
String getUploadUrl(Long id, Integer partNumber);
/**
* 取消文件任务
* @param taskIds
*/
void cancelBatchByIds(List<Long> taskIds);
/**
* 更新文件任务状态为失败
* @param id
* @return
*/
FileTaskUpdateVO updateFailed(Long id);
/**
* 根据id列表更新文件任务状态为暂停
* @param param
* @return
*/
boolean updatePaused(List<Long> param);
}

View File

@@ -0,0 +1,19 @@
package org.springblade.file.service;
import org.springblade.file.pojo.dto.FileCertificateBatchRecognitionDTO;
import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO;
/**
* ocr转换
* @author bfhuange
* @since 2025/3/13
*/
public interface IOCRConvertService {
/**
* 凭证批量识别,不分类
* @param param
* @return
*/
TransportCertificateVO recognitionTransportCertificate(FileCertificateBatchRecognitionDTO param);
}

View File

@@ -0,0 +1,255 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.file.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.CollectionUtil;
import org.springblade.core.tool.utils.SpringUtil;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.file.config.ObsProperties;
import org.springblade.file.convert.BusinessAttachmentConvert;
import org.springblade.file.exception.UnzipFailException;
import org.springblade.file.mapper.AttachmentMapper;
import org.springblade.file.pojo.entity.Attachment;
import org.springblade.file.pojo.vo.AttachmentDetailVO;
import org.springblade.file.pojo.vo.FileHandleVO;
import org.springblade.file.pojo.vo.FileVO;
import org.springblade.file.service.IAttachmentService;
import org.springblade.file.service.IFileHandleService;
import org.springblade.file.service.IFileService;
import org.springblade.file.util.ObjectKeyUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 服务实现类
* @author zhaowei
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class AttachmentServiceImpl extends ServiceImpl<AttachmentMapper, Attachment> implements IAttachmentService {
private final IFileService fileService;
private final BusinessAttachmentConvert convert;
private final ObsProperties obsProperties;
private final IFileHandleService fileHandleService;
private IAttachmentService self;
@Override
public R<List<Attachment>> batchOssUpload(MultipartFile[] files, String overWriteFileName){
try {
List<Attachment> attachmentList = new ArrayList<>();
// 文件数量为1个且重写的文件名不为空使用重写的文件名给uniapp上传使用uniapp上传的文件名不是原始文件名
boolean useOverWriteFileName = files.length == 1 && StringUtil.isNotBlank(overWriteFileName);
for(MultipartFile file : files){
String fileName = useOverWriteFileName ? overWriteFileName : file.getOriginalFilename();
String objectkey = ObjectKeyUtils.generateObjectKey(fileName);
fileService.uploadFile(objectkey, file.getInputStream());
// 插入附件数据
Attachment attachment = new Attachment();
Long id = IdWorker.getId();
attachment.setId(id);
attachment.setAttachmentName(fileName);
attachment.setSize(file.getSize());
attachment.setObjectKey(objectkey);
attachmentList.add(attachment);
}
this.saveBatch(attachmentList);
log.info("OBS上传 successfully");
return R.data(attachmentList,"OBS上传 successfully");
} catch (Exception e) {
log.error("OBS上传 failed,Exception", e);
}
return R.fail("OBS上传 failed");
}
@Override
public List<Attachment> getAttachment(String id) {
return this.list(new LambdaQueryWrapper<Attachment>()
.in(Attachment::getId, Arrays.stream(id.split(",")).toList())
);
}
@Override
public List<Attachment> batchOssUploadUnzipFilesAndDelete(List<File> files, File workDir) {
if (CollectionUtil.isEmpty(files)) {
return Collections.emptyList();
}
List<Attachment> attachmentList = new ArrayList<>();
AtomicInteger index = new AtomicInteger(1);
try {
log.info("上传文件总数:{}", files.size());
for(File file : files){
String fileName = file.getName();
// 压缩文件所在压缩包父目录相对路径
String dir = this.getDir(workDir, file);
// 获取上传的目录前缀
String prefixDir = getUploadPrefix();
String objectkey = prefixDir + ObjectKeyUtils.generateObjectKey(fileName);
fileService.uploadFile(objectkey, file);
int completeFiles = index.getAndIncrement();
if (completeFiles % 10 == 0) {
log.info("上传文件总进度:{}/{}", completeFiles, files.size());
}
// 插入附件数据
Attachment attachment = new Attachment();
Long id = IdWorker.getId();
attachment.setId(id);
attachment.setDir(dir);
attachment.setAttachmentName(fileName);
attachment.setSize(file.length());
attachment.setObjectKey(objectkey);
attachmentList.add(attachment);
}
log.info("上传文件完成:{}", files.size());
// 新事务批量保存
this.getSelf().batchSaveWithNewTransaction(attachmentList);
log.info("OBS上传 successfully");
} catch (Exception e) {
log.error("OBS上传失败", e);
throw new ServiceException("OBS上传失败");
}
// 返回新增的附件列表
return attachmentList;
}
/**
* 获取上传文件目录前缀
* @return
*/
private String getUploadPrefix() {
// 上传路径前缀 = 配置的解压上传路径 + /
return obsProperties.getUnzipUploadDir() + StringPool.SLASH;
}
/**
* 获取压缩文件所在压缩包父目录的相对路径,结尾不带/
* @param workDir 工作目录,解压的根目录
* @param file 解压后的文件
* @return
*/
private String getDir(File workDir, File file) {
if (workDir == null) {
return StringPool.EMPTY;
}
Path workDirPath = workDir.toPath();
Path filePath = file.toPath();
return workDirPath.relativize(filePath).toString()
// 去掉文件名以及文件所在父目录的后缀/或\
.replace(File.separator + file.getName(), "")
// 将文件分隔符替换为/windows下是\,需要处理
.replace(File.separator, StringPool.SLASH);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
@Override
public void batchSaveWithNewTransaction(List<Attachment> list) {
this.saveBatch(list);
}
@Override
public AttachmentDetailVO getDetail(Long id) {
if (id == null) {
return null;
}
Attachment attachment = this.getById(id);
if (attachment == null) {
return null;
}
AttachmentDetailVO detailVO = convert.entity2vo(attachment);
// 查询下载链接
String fileUrl = fileService.getFileUrl(attachment.getObjectKey(), null);
detailVO.setUrl(fileUrl);
return detailVO;
}
@Override
public List<Attachment> unzipAndUpload(FileVO file) {
// 1. 下载并解压文件
try {
FileHandleVO fileHandleVO = fileHandleService.downloadAndUnzip(file);
List<File> unzippedFiles = fileHandleVO.getUnzippedFiles();
if (CollectionUtil.isEmpty(unzippedFiles)) {
log.warn("下载解压文件为空 objectKey:{}", file.getObjectKey());
throw new UnzipFailException("解压文件为空", fileHandleVO.getUnzippedDir());
}
File unzippedDir = fileHandleVO.getUnzippedDir();
// 2. 上传文件
long start = System.currentTimeMillis();
try {
List<Attachment> attachments = getSelf().batchOssUploadUnzipFilesAndDelete(unzippedFiles, unzippedDir);
if (CollectionUtil.isEmpty(attachments)) {
log.warn("上传解压文件为空 objectKey:{}", file.getObjectKey());
return Collections.emptyList();
}
// 删除下载的压缩文件
fileHandleVO.getDownloadFile().delete();
// 删除解压后的目录
FileUtils.deleteQuietly(unzippedDir);
return attachments;
} finally {
long end = System.currentTimeMillis();
log.info("文件:{} 解压后上传完成 耗时:{}", file.getObjectKey(), end - start);
}
} catch (UnzipFailException e) {
// 文件解压失败,删除解压后的文件
File unzipDir = e.getUnzipDir();
// 删除解压后的目录
FileUtils.deleteQuietly(unzipDir);
throw e;
}
}
/**
* 获取自己
* @return
*/
private IAttachmentService getSelf() {
if (self == null) {
self = SpringUtil.getBean(IAttachmentService.class);
}
return self;
}
}

View File

@@ -0,0 +1,136 @@
package org.springblade.file.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.file.convert.BusinessAttachmentConvert;
import org.springblade.file.mapper.BusinessAttachmentMapper;
import org.springblade.file.pojo.dto.BusinessAttachmentAddDTO;
import org.springblade.file.pojo.dto.BusinessAttachmentBatchAddDTO;
import org.springblade.file.pojo.dto.BusinessAttachmentDeleteDTO;
import org.springblade.file.pojo.dto.BusinessAttachmentQueryDTO;
import org.springblade.file.pojo.entity.BusinessAttachment;
import org.springblade.file.pojo.vo.BusinessAttachmentVO;
import org.springblade.file.service.IBusinessAttachmentService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 业务附件信息表 服务实现类
*
* @author BladeX
* @since 2024-09-04
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class BusinessAttachmentServiceImpl extends ServiceImpl<BusinessAttachmentMapper, BusinessAttachment> implements IBusinessAttachmentService {
private final BusinessAttachmentConvert convert;
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveAndRemove(BusinessAttachmentBatchAddDTO param) {
if (param == null) {
log.warn("批量保存业务附件,参数为空");
return false;
}
if (param.isRemove() && param.getBizId() != null) {
// 先删除
this.removeByBizIds(List.of(param.getBizId()));
}
List<BusinessAttachment> addList = convert.dto2entities(param);
if (CollectionUtil.isNotEmpty(addList)) {
return this.saveBatch(addList);
}
return true;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveAndRemove(List<BusinessAttachmentAddDTO> params, BusinessAttachmentDeleteDTO deleteParam) {
boolean hasBizIds = deleteParam != null && deleteParam.isDelete() && CollectionUtil.isNotEmpty(deleteParam.getBizIds());
if (hasBizIds) {
// bizIds不为空先删除
this.removeByBizIdsAndCodes(deleteParam.getBizIds(), deleteParam.getBizCodes());
}
if (CollectionUtil.isEmpty(params)) {
log.warn("保存业务附件,参数为空");
return true;
}
if (!hasBizIds && (deleteParam == null || deleteParam.isDelete())) {
// bizIds为空从参数提取bizIds再删除
// 删除参数为空默认删除删除参数中delete为true默认删除false不删除
List<Long> bizIds = params.stream()
.map(BusinessAttachmentAddDTO::getBizId)
.distinct()
.toList();
this.removeByBizIds(bizIds);
}
List<BusinessAttachment> businessAttachments = convert.dtos2entities(params);
// 按照业务id分组排序
Map<Long, List<BusinessAttachment>> bizIdAttachmentsMap = businessAttachments.stream()
.collect(Collectors.groupingBy(BusinessAttachment::getBizId));
String tenantId = AuthUtil.getTenantId();
for (Map.Entry<Long, List<BusinessAttachment>> entry : bizIdAttachmentsMap.entrySet()) {
int i = 1;
for (BusinessAttachment businessAttachment : entry.getValue()) {
businessAttachment.setId(IdWorker.getId());
// 处理排序
businessAttachment.setSort(i++);
if (StringUtil.isBlank(tenantId)) {
businessAttachment.setTenantId(BladeConstant.ADMIN_TENANT_ID);
}
}
}
return this.saveBatch(businessAttachments);
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean removeByBizIds(List<Long> bizIds) {
return this.removeByBizIdsAndCodes(bizIds, null);
}
@Override
public List<BusinessAttachmentVO> queryList(BusinessAttachmentQueryDTO param) {
return baseMapper.queryList(param);
}
/**
* 批量删除
* @param bizIds 业务id列表为空不删除
* @param bizCodes 业务编码列表,为空,删除所有
* @return
*/
private boolean removeByBizIdsAndCodes(List<Long> bizIds, List<String> bizCodes) {
if (CollectionUtil.isEmpty(bizIds)) {
log.warn("删除附件,参数为空");
return false;
}
List<Long> ids = this.list(Wrappers.<BusinessAttachment>lambdaQuery()
.in(BusinessAttachment::getBizId, bizIds)
// 业务编码列表不为空,添加业务编码查询条件
.in(CollectionUtil.isNotEmpty(bizCodes), BusinessAttachment::getBizCode, bizCodes)
).stream()
.map(BusinessAttachment::getId)
.toList();
if (CollectionUtil.isNotEmpty(ids)) {
return this.removeBatchByIds(ids);
}
return true;
}
}

View File

@@ -0,0 +1,71 @@
package org.springblade.file.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.utils.CollectionUtil;
import org.springblade.file.exception.UnzipFailException;
import org.springblade.file.listener.UnzipCompletedEvent;
import org.springblade.file.listener.UnzipFailEvent;
import org.springblade.file.mapper.BusinessUnzipDetailMapper;
import org.springblade.file.pojo.entity.Attachment;
import org.springblade.file.pojo.entity.BusinessUnzipDetail;
import org.springblade.file.pojo.vo.FileVO;
import org.springblade.file.service.IAttachmentService;
import org.springblade.file.service.IBusinessUnzipDetailService;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author bfhuange
* @date 2024/10/30
*/
@AllArgsConstructor
@Slf4j
@Service
public class BusinessUnzipDetailServiceImpl extends ServiceImpl<BusinessUnzipDetailMapper, BusinessUnzipDetail> implements IBusinessUnzipDetailService {
private final IAttachmentService attachmentService;
private final ApplicationContext applicationContext;
@Async
@Override
public void unzipAndSaveAsync(Long unzipTaskId, FileVO file) {
log.info("解压文件并上传开始 unzipTaskId: {} file: {}", unzipTaskId, JSON.toJSONString(file));
long start = System.currentTimeMillis();
try {
// 1. 上传文件并解压
List<Attachment> attachments = attachmentService.unzipAndUpload(file);
if (CollectionUtil.isEmpty(attachments)) {
log.warn("上传解压文件为空 unzipTaskId:{} objectKey:{}", unzipTaskId, file.getObjectKey());
return;
}
// 2. 保存压缩文件和解压文件关系
List<BusinessUnzipDetail> addParams = attachments.stream()
.map(attachment -> {
BusinessUnzipDetail addParam = new BusinessUnzipDetail();
addParam.setBusinessUnzipTaskId(unzipTaskId);
addParam.setAttachmentId(attachment.getId());
return addParam;
}).toList();
if (CollectionUtil.isNotEmpty(addParams)) {
this.saveBatch(addParams);
}
// 3. 发布解压完成事件
applicationContext.publishEvent(new UnzipCompletedEvent(unzipTaskId));
} catch (UnzipFailException e) {
log.error("解压文件异常", e);
// 发布解压失败事件
applicationContext.publishEvent(new UnzipFailEvent(unzipTaskId));
} catch (Exception e) {
log.error("解压文件并上传异常", e);
} finally {
long end = System.currentTimeMillis();
log.info("解压文件并上传 完成 unzipTaskId: {} objectKey: {} 耗时:{}", unzipTaskId, file.getObjectKey(), end - start);
}
}
}

View File

@@ -0,0 +1,229 @@
package org.springblade.file.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.common.constant.DictTypeEnum;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.log.utils.AssertUtils;
import org.springblade.core.tool.utils.CollectionUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.file.convert.FileTaskConvert;
import org.springblade.file.listener.UnzipStartEvent;
import org.springblade.file.pojo.dto.BusinessUnzipTaskAddDTO;
import org.springblade.file.pojo.dto.BusinessUnzipTaskQueryDTO;
import org.springblade.file.pojo.dto.BusinessUnzipTaskUpdateDTO;
import org.springblade.file.pojo.entity.Attachment;
import org.springblade.file.pojo.entity.BusinessUnzipTask;
import org.springblade.file.mapper.BusinessUnzipTaskMapper;
import org.springblade.file.pojo.entity.FileTask;
import org.springblade.file.pojo.enums.FileTaskStatus;
import org.springblade.file.pojo.enums.UnzipTask;
import org.springblade.file.pojo.vo.BusinessUnzipDetailListVO;
import org.springblade.file.pojo.vo.BusinessUnzipTaskListVO;
import org.springblade.file.pojo.vo.FileVO;
import org.springblade.file.service.IAttachmentService;
import org.springblade.file.service.IBusinessUnzipDetailService;
import org.springblade.file.service.IBusinessUnzipTaskService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springblade.file.service.IFileTaskService;
import org.springblade.system.cache.DictCache;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
/**
* <p>
* 业务解压任务表 服务实现类
* </p>
*
* @author bfhuange
* @since 2024-10-28
*/
@Slf4j
@AllArgsConstructor
@Service
public class BusinessUnzipTaskServiceImpl extends ServiceImpl<BusinessUnzipTaskMapper, BusinessUnzipTask> implements IBusinessUnzipTaskService {
private final FileTaskConvert convert;
private final IFileTaskService fileTaskService;
private final IBusinessUnzipDetailService detailService;
private final IAttachmentService attachmentService;
private final ApplicationContext applicationContext;
@Transactional(rollbackFor = Exception.class)
@Override
public boolean save(BusinessUnzipTaskAddDTO param) {
BusinessUnzipTask addParam = convert.dto2entity(param);
String status = getStatus(param.getRelateType(), param.getRelateId());
addParam.setStatus(status);
boolean result = this.save(addParam);
if (result && UnzipTask.Status.UPLOADED.getCode().equals(status)) {
// 解压任务状态是上传完成,发布解压开始事件
applicationContext.publishEvent(new UnzipStartEvent(addParam));
}
return result;
}
@Override
public String getObjectKey(BusinessUnzipTask task) {
return this.getFile(task).getObjectKey();
}
@Override
public FileVO getFile(BusinessUnzipTask task) {
AssertUtils.notNull(task, "文件任务不存在");
String relateType = task.getRelateType();
AssertUtils.notBlank(relateType, "文件任务关联类型不能为空");
Long relateId = task.getRelateId();
AssertUtils.notNull(relateId, "文件任务关联id不能为空");
if (UnzipTask.RelateType.FILE_TASK.getCode().equals(relateType)) {
// 文件任务关联类型
FileTask fileTask = fileTaskService.getById(relateId);
AssertUtils.notNull(fileTask, "文件任务不存在");
return new FileVO(fileTask.getObjectKey(), fileTask.getMd5());
}
if (UnzipTask.RelateType.ATTACHMENT.getCode().equals(relateType)) {
// 附件关联类型
Attachment attachment = attachmentService.getById(relateId);
AssertUtils.notNull(attachment, "附件不存在");
// 附件暂时没有md5
return new FileVO(attachment.getObjectKey(), null);
}
throw new ServiceException("文件任务关联类型错误");
}
@Override
public IPage<BusinessUnzipTaskListVO> selectPage(IPage<BusinessUnzipTask> page, BusinessUnzipTaskQueryDTO param) {
List<BusinessUnzipTaskListVO> list = baseMapper.selectTaskList(page, param);
if (CollectionUtil.isNotEmpty(list)) {
// 处理状态枚举值名称
list.forEach(vo -> {
String status = vo.getStatus();
if (StringUtil.isNotBlank(status)) {
vo.setStatusStr(DictCache.getValue(DictTypeEnum.FILE_TASK_STATUS.getType(), vo.getStatus()));
}
});
}
IPage<BusinessUnzipTaskListVO> newPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
return newPage.setRecords(list);
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean updateReupload(BusinessUnzipTaskUpdateDTO param) {
log.info("修改业务解压任务重新上传参数:{}", JSON.toJSONString(param));
if (param == null || (param.getId() == null && param.getBizId() == null)) {
throw new ServiceException("参数不能为空");
}
// 校验业务解压任务
BusinessUnzipTask unzipTask = param.getId() != null ? this.getById(param.getId()) : this.getOne(Wrappers.<BusinessUnzipTask>lambdaQuery()
.eq(BusinessUnzipTask::getBizId, param.getBizId())
.last("limit 1")
);
AssertUtils.notNull(unzipTask, "业务任务不存在");
// 上传中或解压失败才可以重新上传
AssertUtils.isTrue(UnzipTask.Status.UPLOADING.getCode().equals(unzipTask.getStatus())
|| UnzipTask.Status.UNZIP_FAIL.getCode().equals(unzipTask.getStatus()), "只有上传中或解压失败的任务才可以重新上传");
if (UnzipTask.Status.UPLOADING.getCode().equals(unzipTask.getStatus())) {
// 上传中的才需要取消上传任务,解压失败的无需取消
try {
// 取消文件任务
fileTaskService.cancelBatchByIds(Lists.newArrayList(unzipTask.getRelateId()));
} catch (Exception e) {
log.error("取消上传异常", e);
}
}
// 更新业务解压任务
BusinessUnzipTask updateParam = new BusinessUnzipTask();
updateParam.setId(unzipTask.getId());
updateParam.setRelateId(param.getRelateId());
String status = getStatus(param.getRelateType(), param.getRelateId());
updateParam.setStatus(status);
boolean result = this.updateById(updateParam);
if (result && UnzipTask.Status.UPLOADED.getCode().equals(status)) {
// 重新查询,防止解压开始事件中查询时事务未提交查询不到最新的数据
unzipTask = this.getById(unzipTask.getId());
// 解压任务状态是上传完成,发布解压开始事件
applicationContext.publishEvent(new UnzipStartEvent(unzipTask));
}
return result;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean cancelBusinessUnzipTask(Long bizId, boolean cancelFile) {
List<BusinessUnzipTask> list = this.list(Wrappers.<BusinessUnzipTask>lambdaQuery()
.eq(BusinessUnzipTask::getBizId, bizId)
);
if (CollectionUtil.isEmpty(list)) {
return true;
}
if (cancelFile) {
boolean hasUpload = list.stream()
.anyMatch(task -> !UnzipTask.Status.UPLOADING.getCode().equals(task.getStatus()));
AssertUtils.isFalse(hasUpload, "当前任务已上传完成,不能取消");
// 取消文件任务
List<Long> taskIds = list.stream()
.filter(task -> UnzipTask.RelateType.FILE_TASK.getCode().equals(task.getRelateType()))
.map(BusinessUnzipTask::getRelateId)
.toList();
fileTaskService.cancelBatchByIds(taskIds);
}
// 删除业务文件解压任务
List<Long> unzipTaskIds = list.stream()
.map(BusinessUnzipTask::getId)
.toList();
this.removeBatchByIds(unzipTaskIds);
return true;
}
@Override
public List<BusinessUnzipDetailListVO> queryBusinessUnzipDetail(Long bizId) {
return baseMapper.queryBusinessUnzipDetail(bizId);
}
@Override
public List<BusinessUnzipTaskListVO> getBusinessUnzipTasksByBizIds(List<Long> bizIds) {
if (CollectionUtil.isEmpty(bizIds)) {
return Collections.emptyList();
}
BusinessUnzipTaskQueryDTO param = new BusinessUnzipTaskQueryDTO();
param.setBizIds(bizIds);
return baseMapper.selectTaskList(null, param);
}
/**
* 获取解压任务状态
* @param relateType 关联类型
* @param relateId 关联id
* @return
*/
private String getStatus(String relateType, Long relateId) {
if (UnzipTask.RelateType.ATTACHMENT.getCode().equals(relateType)) {
// 关联类型是附件,状态直接就是上传完成
return UnzipTask.Status.UPLOADED.getCode();
}
if (UnzipTask.RelateType.FILE_TASK.getCode().equals(relateType)) {
// 关联类型是文件任务,查询文件任务状态
FileTask fileTask = fileTaskService.getById(relateId);
AssertUtils.notNull(fileTask, "文件任务不存在");
if (FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus())) {
// 文件任务上传完成,解压任务设置为上传完成
return UnzipTask.Status.UPLOADED.getCode();
}
// 否则解压任务设置为上传中状态
return UnzipTask.Status.UPLOADING.getCode();
}
throw new ServiceException("关联类型错误");
}
}

View File

@@ -0,0 +1,92 @@
package org.springblade.file.service.impl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.file.config.ObsProperties;
import org.springblade.file.exception.UnzipFailException;
import org.springblade.file.pojo.vo.FileHandleVO;
import org.springblade.file.pojo.vo.FileVO;
import org.springblade.file.service.IFileHandleService;
import org.springblade.file.service.IFileService;
import org.springblade.file.util.UnzipUtil;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.List;
/**
* @author bfhuange
* @date 2024/10/22
*/
@Slf4j
@AllArgsConstructor
@Service
public class FileHandleServiceImpl implements IFileHandleService {
private final IFileService fileService;
private final ObsProperties obsProperties;
@Override
public FileHandleVO downloadAndUnzip(FileVO file) {
File downloadDir = new File(obsProperties.getTmpDownloadDir());
if (!downloadDir.exists()) {
downloadDir.mkdirs();
}
File downloadFile = new File(downloadDir, file.getObjectKey());
String downloadFilePath = downloadFile.getAbsolutePath();
File unzipDir = null;
long unzipStart = System.currentTimeMillis();
try {
// 下载文件
downloadFile(file, downloadFile, downloadFilePath);
// 2. 解压文件目录
unzipDir = new File(obsProperties.getTmpUnzipDir(), file.getObjectKey());
if (!unzipDir.exists()) {
unzipDir.mkdirs();
}
long start = System.currentTimeMillis();
List<File> files = UnzipUtil.unzipOrUnrar(downloadFilePath, unzipDir);
long end = System.currentTimeMillis();
log.info("文件:{} 解压完成 共{}个 耗时:{}", file.getObjectKey(), files.size(), end - start);
FileHandleVO fileHandleVO = new FileHandleVO();
fileHandleVO.setDownloadFile(downloadFile);
fileHandleVO.setUnzippedDir(unzipDir);
fileHandleVO.setUnzippedFiles(files);
return fileHandleVO;
} catch (Exception e) {
log.error("文件解压失败异常", e);
throw new UnzipFailException(e, unzipDir);
} finally {
long unzipEnd = System.currentTimeMillis();
log.info("文件:{} 下载并解压完成 耗时:{}", file.getObjectKey(), unzipEnd - unzipStart);
}
}
/**
* 下载文件
*
* @param file
* @param downloadFile
* @param downloadFilePath
*/
private void downloadFile(FileVO file, File downloadFile, String downloadFilePath) {
if (downloadFile.exists() && file.getMd5() != null) {
// 下载文件存在且md5不为空可能是重新解压
String fileMd5 = fileService.getFileMd5(downloadFile);
if (StringUtil.equals(fileMd5, file.getMd5())) {
log.info("文件存在且md5一致无需重新下载 文件路径:{}", downloadFile.getAbsolutePath());
return;
}
}
long downloadStart = System.currentTimeMillis();
try {
// 1. 下载文件
fileService.downloadFile(file.getObjectKey(), downloadFilePath);
} finally {
long downloadEnd = System.currentTimeMillis();
log.info("文件:{} 下载完成 耗时:{}", file.getObjectKey(), downloadEnd - downloadStart);
}
}
}

View File

@@ -0,0 +1,46 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.file.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.file.mapper.FileTaskPartMapper;
import org.springblade.file.pojo.entity.FileTaskPart;
import org.springblade.file.service.IFileTaskPartService;
import org.springframework.stereotype.Service;
/**
* 服务实现类
*
* @author zhaowei
*/
@Service
@Slf4j
@AllArgsConstructor
public class FileTaskPartServiceImpl extends ServiceImpl<FileTaskPartMapper, FileTaskPart> implements IFileTaskPartService {
}

View File

@@ -0,0 +1,360 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.file.service.impl;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.log.utils.AssertUtils;
import org.springblade.core.tool.utils.CollectionUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.file.config.ObsProperties;
import org.springblade.file.convert.FileTaskConvert;
import org.springblade.file.listener.FileUploadedEvent;
import org.springblade.file.mapper.BusinessUnzipTaskMapper;
import org.springblade.file.mapper.FileTaskMapper;
import org.springblade.file.pojo.dto.FileTaskCreateDTO;
import org.springblade.file.pojo.dto.FileTaskUpdateDTO;
import org.springblade.file.pojo.entity.BusinessUnzipTask;
import org.springblade.file.pojo.entity.FileTask;
import org.springblade.file.pojo.entity.FileTaskPart;
import org.springblade.file.pojo.enums.FileTaskStatus;
import org.springblade.file.pojo.vo.FileTaskUpdateVO;
import org.springblade.file.pojo.vo.PartEtagVO;
import org.springblade.file.service.IFileService;
import org.springblade.file.service.IFileTaskPartService;
import org.springblade.file.service.IFileTaskService;
import org.springblade.file.util.ObjectKeyUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 服务实现类
*
* @author zhaowei
*/
@Service
@Slf4j
@AllArgsConstructor
public class FileTaskServiceImpl extends ServiceImpl<FileTaskMapper, FileTask> implements IFileTaskService {
private final FileTaskConvert convert;
private final IFileTaskPartService partService;
private final IFileService fileService;
private final BusinessUnzipTaskMapper unzipTaskMapper;
private final ObsProperties obsProperties;
private final ApplicationContext applicationContext;
@Override
public FileTaskUpdateVO queryFileTask(Long id, String md5) {
if (id == null && StringUtil.isBlank(md5)) {
throw new ServiceException("id和md5不能都为空");
}
// 根据md5按创建时间倒序取一条
FileTask fileTask = this.getOne(Wrappers.<FileTask>lambdaQuery()
.eq(id != null, FileTask::getId, id)
.eq(StringUtil.isNotBlank(md5), FileTask::getMd5, md5)
.orderByDesc(FileTask::getCreateTime)
.last("limit 1")
);
if (fileTask == null) {
return null;
}
return getFileTaskUpdateVO(fileTask);
}
@Transactional(rollbackFor = Exception.class)
@Override
public Long createFileTask(FileTaskCreateDTO param) {
if (!param.isForce()) {
// 不是强制创建新任务,文件任务存在就报错
FileTask fileTask = this.getOne(Wrappers.<FileTask>lambdaQuery()
.eq(FileTask::getMd5, param.getMd5())
.last("limit 1")
);
AssertUtils.isNull(fileTask, "文件任务已存在");
}
// 1. 初始化上传任务
String fileName = param.getAttachmentName();
String fileSuffix = fileName.substring(fileName.lastIndexOf("."));
String objectKey = ObjectKeyUtils.generateObjectKey(obsProperties.getZipUploadDir(), fileName);
String uploadId = fileService.initiateMultipartUpload(objectKey);
// 2. 保存文件任务
FileTask addParam = convert.dto2entity(param);
addParam.setId(IdWorker.getId());
addParam.setObjectKey(objectKey);
addParam.setSuffix(fileSuffix);
// 当前位置
addParam.setCurrentIndex(0);
addParam.setUploadId(uploadId);
// 正在上传
addParam.setStatus(FileTaskStatus.UPLOADING.getCode());
this.save(addParam);
return addParam.getId();
}
@Transactional(rollbackFor = Exception.class)
@Override
public FileTaskUpdateVO updateFileTask(FileTaskUpdateDTO param) {
// 校验文件任务
FileTask fileTask = this.getById(param.getId());
AssertUtils.notNull(fileTask, "文件任务不存在");
AssertUtils.isTrue(!FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus()), "文件任务已完成");
// 1. 新增分段
addPart(param, fileTask);
// 是否最后一段
boolean isLast = fileTask.getChunkTotal().equals(param.getPartNumber());
// 2. 更新文件任务
boolean update = updateTask(param, isLast);
// 3. 处理上传结束
handleFinished(isLast, update, fileTask);
return this.getUpdateVO(param.getId());
}
/**
* 处理结束
* @param isLast
* @param update
* @param fileTask
*/
private void handleFinished(boolean isLast, boolean update, FileTask fileTask) {
if (isLast && update) {
// 最后一段并且更新成功,合并文件
List<FileTaskPart> parts = partService.list(Wrappers.<FileTaskPart>lambdaQuery()
.eq(FileTaskPart::getFileTaskId, fileTask.getId())
.orderByAsc(FileTaskPart::getPartNumber)
);
try {
// 按分段号去重相同分段号取第一个如果分段号相同etag不同报错
Map<Integer, FileTaskPart> numberTag = parts.stream()
.collect(Collectors.toMap(FileTaskPart::getPartNumber, Function.identity(), (a, b) -> {
if (StringUtil.equals(a.getEtag(), b.getEtag())) {
return a;
}
log.error("etag不一致 id1{} id2{} etag1{} etag2{}", a.getId(), b.getId(), a.getEtag(), b.getEtag());
throw new ServiceException("etag不一致");
}));
// 获取要保留的分段id
List<Long> partId = numberTag.values().stream()
.map(FileTaskPart::getId)
.toList();
// 去重
parts = parts.stream()
.filter(e -> partId.contains(e.getId()))
.toList();
List<PartEtagVO> partEtags = parts.stream()
.map(convert::entity2vo)
.toList();
// 3. 合并文件
fileService.completeMultipartUpload(fileTask.getObjectKey(), fileTask.getUploadId(), partEtags);
// 4. 删除文件任务分段
List<Long> partIds = parts.stream()
.map(FileTaskPart::getId)
.toList();
partService.removeByIds(partIds);
// 5. 发布上传完成事件
applicationContext.publishEvent(new FileUploadedEvent(fileTask.getId()));
} catch (Exception e) {
log.error("文件分段上传合并文件异常", e);
log.error("文件任务id{} objectKey{}", fileTask.getId(), fileTask.getObjectKey());
// 更新文件任务状态为失败
FileTask updateParam = new FileTask();
updateParam.setId(fileTask.getId());
updateParam.setStatus(FileTaskStatus.FAILED.getCode());
this.updateById(updateParam);
}
}
}
/**
* 新增分段
* @param param
* @param fileTask
*/
private void addPart(FileTaskUpdateDTO param, FileTask fileTask) {
// 校验分段
FileTaskPart part = partService.getOne(Wrappers.<FileTaskPart>lambdaQuery()
.eq(FileTaskPart::getFileTaskId, fileTask.getId())
.eq(FileTaskPart::getPartNumber, param.getPartNumber())
.last("limit 1")
);
if (part != null) {
// 分段存在
if (StringUtil.equals(part.getEtag(), param.getEtag())) {
// 且etag和当前的etag相同不做任何处理
return;
}
// etag和当前的etag不同报错
throw new ServiceException("当前分段已存在");
}
// 新增分段
FileTaskPart addParam = new FileTaskPart();
addParam.setId(IdWorker.getId());
addParam.setFileTaskId(fileTask.getId());
addParam.setPartNumber(param.getPartNumber());
addParam.setEtag(param.getEtag());
partService.save(addParam);
}
@Override
public String getUploadUrl(Long id, Integer partNumber) {
FileTask fileTask = this.getById(id);
AssertUtils.notNull(fileTask, "文件任务不存在");
AssertUtils.isTrue(!FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus()), "文件任务已完成");
return fileService.getUploadPartUrl(fileTask.getObjectKey(), fileTask.getUploadId(), partNumber, null);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void cancelBatchByIds(List<Long> taskIds) {
// 查询任务
List<FileTask> fileTasks = this.listByIds(taskIds);
if (CollectionUtil.isEmpty(fileTasks)) {
return;
}
// 校验状态
fileTasks.forEach(task -> AssertUtils.isFalse(FileTaskStatus.COMPLETED.getCode().equals(task.getStatus()), "文件任务已上传完成"));
// 调用obs取消上传任务
try {
for (FileTask fileTask : fileTasks) {
fileService.cancelMultipartUpload(fileTask.getObjectKey(), fileTask.getUploadId());
}
} catch (Exception e) {
log.error("调用obs取消文件上传任务异常", e);
throw new ServiceException("调用obs取消文件上传任务异常");
}
// 删除上传任务
this.removeBatchByIds(taskIds);
// 删除文件任务分片
List<FileTaskPart> list = partService.list(Wrappers.<FileTaskPart>lambdaQuery()
.in(FileTaskPart::getFileTaskId, taskIds)
);
if (CollectionUtil.isNotEmpty(list)) {
List<Long> partIds = list.stream()
.map(FileTaskPart::getId)
.toList();
partService.removeByIds(partIds);
}
}
@Override
public FileTaskUpdateVO updateFailed(Long id) {
// 校验文件任务
FileTask fileTask = this.getById(id);
AssertUtils.notNull(fileTask, "文件任务不存在");
AssertUtils.isTrue(!FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus()), "文件任务已完成");
// 更新文件任务状态为失败
FileTask updateParam = new FileTask();
updateParam.setId(id);
updateParam.setStatus(FileTaskStatus.FAILED.getCode());
this.updateById(updateParam);
return this.getUpdateVO(id);
}
@Override
public boolean updatePaused(List<Long> ids) {
if (CollectionUtil.isEmpty(ids)) {
return false;
}
FileTask updateParam = new FileTask();
// 修改状态为已暂停
updateParam.setStatus(FileTaskStatus.PAUSED.getCode());
return this.update(updateParam, Wrappers.<FileTask>lambdaUpdate()
.in(FileTask::getId, ids)
// 只更新正在上传状态的
.eq(FileTask::getStatus, FileTaskStatus.UPLOADING.getCode())
);
}
/**
* 查询文件任务
* @param id
* @return
*/
private FileTaskUpdateVO getUpdateVO(Long id) {
FileTask fileTask = this.getById(id);
return getFileTaskUpdateVO(fileTask);
}
/**
* 查询文件任务
* @param fileTask
* @return
*/
private FileTaskUpdateVO getFileTaskUpdateVO(FileTask fileTask) {
if (fileTask == null) {
return null;
}
FileTaskUpdateVO updateVO = convert.entity2updateVo(fileTask);
BusinessUnzipTask businessUnzipTask = unzipTaskMapper.selectOne(Wrappers.<BusinessUnzipTask>lambdaQuery()
.eq(BusinessUnzipTask::getRelateId, fileTask.getId())
.last("limit 1")
);
if (businessUnzipTask != null && businessUnzipTask.getDocCode() != null) {
updateVO.setDocCode(businessUnzipTask.getDocCode());
}
return updateVO;
}
/**
* 更新任务
*
* @param param
* @param isLast 是否最后一段
* @return
*/
private boolean updateTask(FileTaskUpdateDTO param, boolean isLast) {
FileTask updateParam = new FileTask();
updateParam.setCurrentIndex(param.getPartNumber());
if (isLast) {
// 最后一段,修改任务状态为完成
updateParam.setStatus(FileTaskStatus.COMPLETED.getCode());
} else {
// 非最后一段,修改任务状态为上传中
updateParam.setStatus(FileTaskStatus.UPLOADING.getCode());
}
return this.update(updateParam, Wrappers.<FileTask>lambdaUpdate()
.eq(FileTask::getId, param.getId())
.lt(FileTask::getCurrentIndex, param.getPartNumber())
);
}
}

View File

@@ -0,0 +1,40 @@
package org.springblade.file.service.impl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.utils.AssertUtils;
import org.springblade.file.convert.OCRConverter;
import org.springblade.file.pojo.dto.FileCertificateBatchRecognitionDTO;
import org.springblade.file.service.IFileService;
import org.springblade.file.service.IOCRConvertService;
import org.springblade.thirdparty.ocr.pojo.dto.CertificateBatchRecognitionDTO;
import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO;
import org.springblade.thirdparty.ocr.service.IOCRService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author bfhuange
* @since 2025/3/13
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class OCRConvertServiceImpl implements IOCRConvertService {
private final IFileService fileService;
private final IOCRService ocrService;
private final OCRConverter converter;
@Override
public TransportCertificateVO recognitionTransportCertificate(FileCertificateBatchRecognitionDTO param) {
AssertUtils.notNull(param, "参数不能为空");
AssertUtils.notNull(param.getObjectKeys(), "图像obs key不能为空");
CertificateBatchRecognitionDTO ocrParam = converter.dto2ocr(param);
// 转换文件链接
List<String> fileUrls = fileService.getFileUrls(param.getObjectKeys(), null);
ocrParam.setUrls(fileUrls);
return ocrService.recognitionTransportCertificate(ocrParam);
}
}

View File

@@ -0,0 +1,293 @@
package org.springblade.file.service.impl;
import cn.hutool.core.net.URLEncodeUtil;
import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.internal.Constants;
import com.obs.services.internal.utils.ServiceUtils;
import com.obs.services.model.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.tool.utils.CollectionUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.file.config.ObsProperties;
import org.springblade.file.pojo.vo.PartEtagVO;
import org.springblade.file.service.IFileService;
import org.springblade.file.service.support.DownloadProgressListener;
import org.springblade.file.service.support.UploadProgressListener;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
/**
* 华为obs对象存储
* @author bfhuange
* @date 2024/10/21
*/
@Slf4j
@RequiredArgsConstructor
public class ObsFileServiceImpl implements IFileService {
private final ObsProperties properties;
private final ObsClient obsClient;
@Override
public void uploadFile(String objectKey, InputStream is) {
this.uploadFile(objectKey, null, is, null);
}
@Override
public String uploadTempFile(String objectKey, InputStream is, Integer expires) {
// 上传文件
this.uploadFile(objectKey, null, is, expires);
// 生成下载链接
return this.getFileUrl(objectKey, properties.getExpires());
}
@Override
public void uploadFile(String objectKey, File file) {
this.uploadFile(objectKey, file, null, null);
}
@Override
public String initiateMultipartUpload(String objectKey) {
InitiateMultipartUploadRequest initiateRequest = new InitiateMultipartUploadRequest(properties.getBucketName(), objectKey);
try {
InitiateMultipartUploadResult initiateResult = obsClient.initiateMultipartUpload(initiateRequest);
return initiateResult.getUploadId();
} catch (Exception e) {
log.error("初始化分段上传异常", e);
throw new ServiceException("初始化分段上传异常");
}
}
@Override
public PartEtagVO uploadPart(String objectKey, String uploadId, File file, int partNumber, Long offset, Long partSize) {
UploadPartRequest uploadPartRequest = new UploadPartRequest();
uploadPartRequest.setBucketName(properties.getBucketName());
uploadPartRequest.setObjectKey(objectKey);
uploadPartRequest.setUploadId(uploadId);
uploadPartRequest.setPartNumber(partNumber);
uploadPartRequest.setFile(file);
if (offset != null) {
uploadPartRequest.setOffset(offset);
}
if (partSize != null) {
uploadPartRequest.setPartSize(partSize);
}
try {
UploadPartResult uploadPartResult = obsClient.uploadPart(uploadPartRequest);
return new PartEtagVO(uploadPartResult.getEtag(), uploadPartResult.getPartNumber());
} catch (Exception e) {
log.error("分段上传异常", e);
throw new ServiceException("分段上传异常");
}
}
@Override
public void completeMultipartUpload(String objectKey, String uploadId, List<PartEtagVO> partEtagVOS) {
if (CollectionUtil.isEmpty(partEtagVOS)) {
log.warn("完成分段上传,要合并的分段列表为空");
return;
}
// 参数转换
List<PartEtag> list = partEtagVOS.stream()
.map(partEtagVO -> new PartEtag(partEtagVO.getEtag(), partEtagVO.getPartNumber()))
.collect(Collectors.toCollection(ArrayList::new));
CompleteMultipartUploadRequest request =
new CompleteMultipartUploadRequest(properties.getBucketName(), objectKey, uploadId, list);
try {
obsClient.completeMultipartUpload(request);
} catch (Exception e) {
log.error("完成分段上传异常", e);
throw new ServiceException("完成分段上传异常");
}
}
@Override
public void cancelMultipartUpload(String objectKey, String uploadId) {
AbortMultipartUploadRequest request = new AbortMultipartUploadRequest(properties.getBucketName(), objectKey, uploadId);
try {
obsClient.abortMultipartUpload(request);
} catch (Exception e) {
log.error("取消分段上传任务异常", e);
throw new ServiceException("取消分段上传任务异常");
}
}
@Override
public String getFileUrl(String objectKey, Long expires) {
return this.getFileUrl(objectKey, null, expires);
}
@Override
public String getFileUrl(String objectKey, String fileName, Long expires) {
if (StringUtil.isBlank(objectKey)) {
return null;
}
// 链接失效时间
expires = Optional.ofNullable(expires)
// 参数为空取配置
.orElse(Optional.ofNullable(properties.getExpires())
// 配置为空默认1小时
.orElse(Duration.ofHours(1).getSeconds()));
TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires);
request.setBucketName(properties.getBucketName());
request.setObjectKey(objectKey);
request.setRequestDate(new Date());
if (StringUtil.isNotBlank(fileName)) {
// 文件名不为空,修改响应的文件名
Map<String, Object> queryParam = new HashMap<>();
String encodeFileName = encode(fileName);
queryParam.put(Constants.ObsRequestParams.RESPONSE_CONTENT_DISPOSITION, "attachment;filename*=UTF-8''" + encodeFileName);
request.setQueryParams(queryParam);
}
try {
TemporarySignatureResponse temporarySignature = obsClient.createTemporarySignature(request);
return temporarySignature.getSignedUrl();
} catch (Exception e) {
log.error("获取文件链接异常", e);
throw new ServiceException("获取文件链接异常");
}
}
/**
* 编码文件名
* @param fileName
* @return
*/
private String encode(String fileName) {
int index = fileName.lastIndexOf(".");
if (index < 0) {
return URLEncodeUtil.encode(fileName);
}
// 文件名
String prefix = fileName.substring(0, index);
// 后缀
String ext = fileName.substring(index);
return URLEncodeUtil.encode(prefix) + ext;
}
@Override
public String getUploadPartUrl(String objectKey, String uploadId, Integer partNumber, Long expires) {
TemporarySignatureRequest request = new TemporarySignatureRequest();
request.setBucketName(properties.getBucketName());
request.setObjectKey(objectKey);
request.setRequestDate(new Date());
// 链接失效时间
expires = Optional.ofNullable(expires)
.orElse(properties.getExpires());
if (expires != null) {
request.setExpires(expires);
}
request.setMethod(HttpMethodEnum.PUT);
request.setHeaders(Map.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE));
Map<String, Object> params = new HashMap<>();
params.put(Constants.ObsRequestParams.PART_NUMBER, String.valueOf(partNumber));
params.put(Constants.ObsRequestParams.UPLOAD_ID, uploadId);
request.setQueryParams(params);
try {
TemporarySignatureResponse temporarySignature = obsClient.createTemporarySignature(request);
return temporarySignature.getSignedUrl();
} catch (Exception e) {
log.error("获取文件链接异常", e);
throw new ServiceException("获取文件链接异常");
}
}
@Override
public List<String> getFileUrls(List<String> objectKeys, Long expires) {
if (CollectionUtil.isEmpty(objectKeys)) {
return Collections.emptyList();
}
return objectKeys.stream()
.map(objectKey -> this.getFileUrl(objectKey, expires))
.toList();
}
@Override
public void downloadFile(String objectKey, String downloadFile) {
DownloadFileRequest request = new DownloadFileRequest(properties.getBucketName(), objectKey);
request.setDownloadFile(downloadFile);
// 开启断点续传模式
request.setEnableCheckpoint(true);
Long progressInterval = properties.getDownloadProgressInterval();
// 设置下载进度日志
request.setProgressListener(new DownloadProgressListener(objectKey, progressInterval));
request.setProgressInterval(progressInterval);
DownloadFileResult result = null;
try {
result = obsClient.downloadFile(request);
} catch (ObsException e) {
log.error("下载文件异常", e);
log.error("下载文件异常 objectKey{} responseCode{} errorCode{} errorMessage{} errorRequestId{} errorHostId{}", objectKey, e.getResponseCode(), e.getErrorCode(), e.getErrorMessage(), e.getErrorRequestId(), e.getErrorHostId());
throw new ServiceException("下载文件异常");
} catch (Exception e) {
log.error("下载文件异常", e);
throw new ServiceException("下载文件异常");
} finally {
String etag = Optional.ofNullable(result)
.map(DownloadFileResult::getObjectMetadata)
.map(ObjectMetadata::getEtag)
.orElse(null);
log.info("下载文件结束 objectKey{} etag{}", objectKey, etag);
}
}
/**
* 上传文件
* @param objectKey
* @param file
* @param is
* @param expires 过期时间,单位天,过期后自动删除
*/
private void uploadFile(String objectKey, File file, InputStream is, Integer expires) {
if (file == null && is == null ) {
throw new ServiceException("文件不存在");
}
PutObjectRequest request = new PutObjectRequest();
request.setBucketName(properties.getBucketName());
request.setObjectKey(objectKey);
if (expires != null) {
request.setExpires(expires);
}
if (is != null) {
request.setInput(is);
} else {
request.setFile(file);
// 文件不为空设置md5
String md5 = getFileMd5(file);
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentMd5(md5);
request.setMetadata(metadata);
}
Long progressInterval = properties.getUploadProgressInterval();
// 设置上传进度日志
request.setProgressListener(new UploadProgressListener(objectKey, progressInterval));
request.setProgressInterval(progressInterval);
try {
obsClient.putObject(request);
} catch (Exception e) {
log.error("上传文件异常", e);
throw new ServiceException("上传文件异常");
}
}
@Override
public String getFileMd5(File file) {
try {
return ServiceUtils.toBase64(ServiceUtils.computeMD5Hash(new FileInputStream(file)));
} catch (NoSuchAlgorithmException | IOException e) {
log.error("获取文件md5异常", e);
return null;
}
}
}

View File

@@ -0,0 +1,31 @@
package org.springblade.file.service.support;
import com.obs.services.model.ProgressListener;
import com.obs.services.model.ProgressStatus;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 文件下载进度监听器
* @author bfhuange
* @date 2024/10/28
*/
@AllArgsConstructor
@Slf4j
public class DownloadProgressListener implements ProgressListener {
/**
* obs对象名称
*/
private final String objectKey;
/**
* 进度间隔,如果新传输的大小不等于进度间隔,说明是结束了
*/
private final long progressInterval;
@Override
public void progressChanged(ProgressStatus status) {
log.info("文件下载进度 文件名:{} 是否结束:{} 总大小:{} 已下载大小:{} 百分比:{} 下载速度:{}", objectKey, progressInterval != status.getNewlyTransferredBytes(), status.getTotalBytes(), status.getTransferredBytes(), status.getTransferPercentage(), String.format("%.2f", status.getAverageSpeed()));
}
}

View File

@@ -0,0 +1,31 @@
package org.springblade.file.service.support;
import com.obs.services.model.ProgressListener;
import com.obs.services.model.ProgressStatus;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 文件上传进度监听器
* @author bfhuange
* @date 2024/10/28
*/
@AllArgsConstructor
@Slf4j
public class UploadProgressListener implements ProgressListener {
/**
* obs对象名称
*/
private final String objectKey;
/**
* 进度间隔,如果新传输的大小不等于进度间隔,说明是结束了
*/
private final long progressInterval;
@Override
public void progressChanged(ProgressStatus status) {
log.info("文件上传进度 文件名:{} 是否结束:{} 已上传大小:{} 上传速度:{}", objectKey, progressInterval != status.getNewlyTransferredBytes(), status.getTransferredBytes(), String.format("%.2f", status.getAverageSpeed()));
}
}

Some files were not shown because too many files have changed in this diff Show More