feat(file): Object storage persistence follows preset path rules

This commit is contained in:
smile 2025-09-19 16:04:40 +08:00
parent 3e74da643d
commit 56acac8759
21 changed files with 589 additions and 49 deletions

View file

@ -20,7 +20,7 @@ export type OpenAPIConfig = {
};
export const OpenAPI: OpenAPIConfig = {
BASE: 'http://localhost:9527/file',
BASE: '/file',
VERSION: '1.0.0',
WITH_CREDENTIALS: false,
CREDENTIALS: 'include',

View file

@ -14,6 +14,8 @@ export { BaseVoListVerificationResult } from './models/BaseVoListVerificationRes
export { BaseVoObject } from './models/BaseVoObject';
export { BaseVoPageVoFileEntity } from './models/BaseVoPageVoFileEntity';
export { BaseVoPageVoImageEntity } from './models/BaseVoPageVoImageEntity';
export { BaseVoPageVoVideoEntity } from './models/BaseVoPageVoVideoEntity';
export { BaseVoVideoEntity } from './models/BaseVoVideoEntity';
export type { FileCreateDto } from './models/FileCreateDto';
export type { FileEntity } from './models/FileEntity';
export type { FilePageDto } from './models/FilePageDto';
@ -24,6 +26,11 @@ export type { ImagePageDto } from './models/ImagePageDto';
export type { ImageUpdateDto } from './models/ImageUpdateDto';
export type { PageVoFileEntity } from './models/PageVoFileEntity';
export type { PageVoImageEntity } from './models/PageVoImageEntity';
export type { PageVoVideoEntity } from './models/PageVoVideoEntity';
export type { VerificationResult } from './models/VerificationResult';
export type { VideoCreateDto } from './models/VideoCreateDto';
export type { VideoEntity } from './models/VideoEntity';
export type { VideoPageDto } from './models/VideoPageDto';
export type { VideoUpdateDto } from './models/VideoUpdateDto';
export { Service } from './services/Service';

View file

@ -0,0 +1,39 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { PageVoVideoEntity } from './PageVoVideoEntity';
/**
*
*/
export type BaseVoPageVoVideoEntity = {
/**
*
*/
code?: number;
/**
*
*/
message?: string;
data?: PageVoVideoEntity;
/**
*
*/
time?: string;
/**
*
*/
type?: BaseVoPageVoVideoEntity.type;
};
export namespace BaseVoPageVoVideoEntity {
/**
*
*/
export enum type {
SUCCESS = 'SUCCESS',
WARNING = 'WARNING',
INFO = 'INFO',
ERROR = 'ERROR',
}
}

View file

@ -0,0 +1,39 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { VideoEntity } from './VideoEntity';
/**
*
*/
export type BaseVoVideoEntity = {
/**
*
*/
code?: number;
/**
*
*/
message?: string;
data?: VideoEntity;
/**
*
*/
time?: string;
/**
*
*/
type?: BaseVoVideoEntity.type;
};
export namespace BaseVoVideoEntity {
/**
*
*/
export enum type {
SUCCESS = 'SUCCESS',
WARNING = 'WARNING',
INFO = 'INFO',
ERROR = 'ERROR',
}
}

View file

@ -15,6 +15,9 @@ export type FileCreateDto = {
*/
description?: string;
uploadFile?: Blob;
/**
*
*/
name?: string;
/**
*

View file

@ -9,16 +9,11 @@ export type FileUpdateDto = {
/**
*
*/
id: string;
/**
*
*/
sort?: number;
/**
*
*/
description?: string;
id?: string;
uploadFile?: Blob;
/**
*
*/
name?: string;
/**
*
@ -32,5 +27,13 @@ export type FileUpdateDto = {
*
*/
source?: string;
/**
*
*/
sort?: number;
/**
*
*/
description?: string;
};

View file

@ -0,0 +1,27 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { VideoEntity } from './VideoEntity';
/**
*
*/
export type PageVoVideoEntity = {
/**
*
*/
total?: number;
/**
*
*/
page?: number;
/**
*
*/
size?: number;
/**
*
*/
result?: Array<VideoEntity>;
};

View file

@ -0,0 +1,54 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/**
*
*/
export type VideoCreateDto = {
/**
*
*/
sort?: number;
/**
*
*/
description?: string;
/**
*
*/
name?: string;
/**
*
*/
width?: number;
/**
*
*/
height?: number;
/**
*
*/
duration?: number;
/**
*
*/
format?: string;
/**
*
*/
bitrate?: number;
/**
*
*/
fps?: number;
/**
*
*/
codec?: string;
/**
* id
*/
fileId?: string;
};

View file

@ -0,0 +1,73 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { FileEntity } from './FileEntity';
/**
*
*/
export type VideoEntity = {
id?: string;
/**
*
*/
type?: string;
/**
*
*/
sort?: number;
/**
*
*/
createTime?: string;
/**
*
*/
updateTime?: string;
/**
*
*/
description?: string;
/**
*
*/
deleted?: number;
/**
*
*/
name?: string;
/**
*
*/
width?: number;
/**
*
*/
height?: number;
/**
*
*/
duration?: number;
/**
*
*/
format?: string;
/**
*
*/
bitrate?: number;
/**
*
*/
fps?: number;
/**
*
*/
codec?: string;
/**
* id
*/
fileId?: string;
file?: FileEntity;
};

View file

@ -0,0 +1,34 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/**
*
*/
export type VideoPageDto = {
/**
*
*/
page: number;
/**
*
*/
size: number;
/**
*
*/
name?: string;
/**
*
*/
duration?: number;
/**
*
*/
format?: string;
/**
*
*/
codec?: string;
};

View file

@ -0,0 +1,58 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/**
*
*/
export type VideoUpdateDto = {
/**
*
*/
id: string;
/**
*
*/
sort?: number;
/**
*
*/
description?: string;
/**
*
*/
name?: string;
/**
*
*/
width?: number;
/**
*
*/
height?: number;
/**
*
*/
duration?: number;
/**
*
*/
format?: string;
/**
*
*/
bitrate?: number;
/**
*
*/
fps?: number;
/**
*
*/
codec?: string;
/**
* id
*/
fileId?: string;
};

View file

@ -7,16 +7,61 @@ import type { BaseVoImageEntity } from '../models/BaseVoImageEntity';
import type { BaseVoInteger } from '../models/BaseVoInteger';
import type { BaseVoPageVoFileEntity } from '../models/BaseVoPageVoFileEntity';
import type { BaseVoPageVoImageEntity } from '../models/BaseVoPageVoImageEntity';
import type { BaseVoPageVoVideoEntity } from '../models/BaseVoPageVoVideoEntity';
import type { BaseVoVideoEntity } from '../models/BaseVoVideoEntity';
import type { FileCreateDto } from '../models/FileCreateDto';
import type { FilePageDto } from '../models/FilePageDto';
import type { FileUpdateDto } from '../models/FileUpdateDto';
import type { ImageCreateDto } from '../models/ImageCreateDto';
import type { ImagePageDto } from '../models/ImagePageDto';
import type { ImageUpdateDto } from '../models/ImageUpdateDto';
import type { VideoCreateDto } from '../models/VideoCreateDto';
import type { VideoPageDto } from '../models/VideoPageDto';
import type { VideoUpdateDto } from '../models/VideoUpdateDto';
import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';
export class Service {
/**
*
* @param requestBody
* @returns BaseVoVideoEntity OK
* @throws ApiError
*/
public static updateVideo(
requestBody: VideoUpdateDto,
): CancelablePromise<BaseVoVideoEntity> {
return __request(OpenAPI, {
method: 'PUT',
url: '/video',
body: requestBody,
mediaType: 'application/json',
errors: {
400: `参数校验异常`,
500: `业务异常`,
},
});
}
/**
*
* @param requestBody
* @returns BaseVoVideoEntity OK
* @throws ApiError
*/
public static saveVideo(
requestBody: VideoCreateDto,
): CancelablePromise<BaseVoVideoEntity> {
return __request(OpenAPI, {
method: 'POST',
url: '/video',
body: requestBody,
mediaType: 'application/json',
errors: {
400: `参数校验异常`,
500: `业务异常`,
},
});
}
/**
*
* @param requestBody
@ -59,19 +104,18 @@ export class Service {
}
/**
* |
* @param fileUpdateDto
* @param formData
* @returns BaseVoFileEntity OK
* @throws ApiError
*/
public static updateFile(
fileUpdateDto: FileUpdateDto,
formData?: FileUpdateDto,
): CancelablePromise<BaseVoFileEntity> {
return __request(OpenAPI, {
method: 'PUT',
url: '/file',
query: {
'fileUpdateDto': fileUpdateDto,
},
formData: formData,
mediaType: 'multipart/form-data',
errors: {
400: `参数校验异常`,
500: `业务异常`,
@ -80,19 +124,38 @@ export class Service {
}
/**
* |
* @param fileCreateDto
* @param formData
* @returns BaseVoFileEntity OK
* @throws ApiError
*/
public static saveFile(
fileCreateDto: FileCreateDto,
formData?: FileCreateDto,
): CancelablePromise<BaseVoFileEntity> {
return __request(OpenAPI, {
method: 'POST',
url: '/file',
query: {
'fileCreateDto': fileCreateDto,
formData: formData,
mediaType: 'multipart/form-data',
errors: {
400: `参数校验异常`,
500: `业务异常`,
},
});
}
/**
*
* @param requestBody
* @returns BaseVoPageVoVideoEntity OK
* @throws ApiError
*/
public static findPageVideo(
requestBody: VideoPageDto,
): CancelablePromise<BaseVoPageVoVideoEntity> {
return __request(OpenAPI, {
method: 'POST',
url: '/video/page',
body: requestBody,
mediaType: 'application/json',
errors: {
400: `参数校验异常`,
500: `业务异常`,
@ -139,6 +202,48 @@ export class Service {
},
});
}
/**
*
* @param id
* @returns BaseVoVideoEntity OK
* @throws ApiError
*/
public static findVideoById(
id: string,
): CancelablePromise<BaseVoVideoEntity> {
return __request(OpenAPI, {
method: 'GET',
url: '/video/{id}',
path: {
'id': id,
},
errors: {
400: `参数校验异常`,
500: `业务异常`,
},
});
}
/**
*
* @param id
* @returns binary OK
* @throws ApiError
*/
public static downloadVideo(
id: string,
): CancelablePromise<Blob> {
return __request(OpenAPI, {
method: 'GET',
url: '/video/download/{id}',
path: {
'id': id,
},
errors: {
400: `参数校验异常`,
500: `业务异常`,
},
});
}
/**
*
* @param id
@ -166,7 +271,7 @@ export class Service {
* @returns binary OK
* @throws ApiError
*/
public static downloadFile(
public static downloadImage(
id: string,
): CancelablePromise<Blob> {
return __request(OpenAPI, {
@ -202,13 +307,39 @@ export class Service {
},
});
}
/**
* 线
* @param id
* @param range
* @returns binary OK
* @throws ApiError
*/
public static streamFile(
id: string,
range?: string,
): CancelablePromise<Blob> {
return __request(OpenAPI, {
method: 'GET',
url: '/file/stream/{id}',
path: {
'id': id,
},
headers: {
'Range': range,
},
errors: {
400: `参数校验异常`,
500: `业务异常`,
},
});
}
/**
*
* @param id
* @returns binary OK
* @throws ApiError
*/
public static downloadFile1(
public static downloadFile(
id: string,
): CancelablePromise<Blob> {
return __request(OpenAPI, {
@ -223,6 +354,27 @@ export class Service {
},
});
}
/**
*
* @param ids
* @returns BaseVoInteger<any> OK
* @throws ApiError
*/
public static deleteVideo(
ids: string,
): CancelablePromise<BaseVoInteger> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/video/{ids}',
path: {
'ids': ids,
},
errors: {
400: `参数校验异常`,
500: `业务异常`,
},
});
}
/**
*
* @param ids

View file

@ -430,10 +430,10 @@ export class Service {
}
/**
*
* @returns string OK
* @returns binary OK
* @throws ApiError
*/
public static downloadRoleImportTemplate(): CancelablePromise<string> {
public static downloadRoleImportTemplate(): CancelablePromise<Blob> {
return __request(OpenAPI, {
method: 'GET',
url: '/role/template-download',
@ -509,6 +509,27 @@ export class Service {
},
});
}
/**
*
* @param group
* @returns BaseVoListMenuEntity OK
* @throws ApiError
*/
public static findByMenuGroup(
group: string,
): CancelablePromise<BaseVoListMenuEntity> {
return __request(OpenAPI, {
method: 'GET',
url: '/menu/find-by-menu-group/{group}',
path: {
'group': group,
},
errors: {
400: `参数校验异常`,
500: `业务异常`,
},
});
}
/**
*
* @returns BaseVoListMenuEntity OK

View file

@ -2,7 +2,6 @@ package com.bgasol.common.core.base.service;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bgasol.common.core.base.dto.BasePageDto;

View file

@ -8,6 +8,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.web.multipart.MultipartFile;
import static com.bgasol.model.file.file.mapstruct.FileMapstruct.FILE_MAPSTRUCT_IMPL;
@ -37,6 +38,9 @@ public class FileCreateDto extends BaseCreateDto<FileEntity> {
@JsonIgnore
@Schema(hidden = true)
public FileEntity toEntity() {
if (ObjectUtils.isEmpty(this.source)) {
this.source = "default";
}
return super.toEntity(FILE_MAPSTRUCT_IMPL.toEntity(this));
}
}

View file

@ -32,9 +32,6 @@ public class FileUpdateDto {
@Schema(description = "文件后缀")
private String suffix;
@Schema(description = "文件来源")
private String source;
@Schema(description = "排序")
private Integer sort;

View file

@ -101,7 +101,7 @@ public class FileController extends BaseController<
StandardCharsets.UTF_8
))
.contentType(MediaType.valueOf(file.getType()))
.body(new InputStreamResource(ossService.readFileStream(file.getBucket(), file.getId(), file.getName())));
.body(new InputStreamResource(ossService.readFileStream(file)));
}
@GetMapping("/stream/{id}")
@ -134,7 +134,7 @@ public class FileController extends BaseController<
long contentLength = rangeEnd - rangeStart + 1;
InputStream inputStream = ossService.readFileStream(file.getBucket(), file.getId(), file.getName());
InputStream inputStream = ossService.readFileStream(file);
inputStream.skip(rangeStart); // 跳到起始位置
InputStreamResource resource = new InputStreamResource(inputStream);

View file

@ -19,6 +19,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
@Service
@RequiredArgsConstructor
@ -29,8 +30,6 @@ public class FileService extends BaseService<FileEntity, FilePageDto> {
private final MinioConfig minioConfig;
private final MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap();
private final OssService ossService;
private final RedissonClient redissonClient;
@ -58,7 +57,7 @@ public class FileService extends BaseService<FileEntity, FilePageDto> {
fileEntity = this.save(fileEntity);
// 上传文件
try (InputStream inputStream = multipartFile.getInputStream()) {
ossService.writeFileStream(fileEntity.getBucket(), fileEntity.getId(), fileEntity.getName(), inputStream, fileEntity.getSize(), fileEntity.getType());
ossService.writeFileStream(inputStream, fileEntity);
} catch (IOException e) {
throw new BaseException("上传文件失败");
}
@ -75,7 +74,7 @@ public class FileService extends BaseService<FileEntity, FilePageDto> {
fileEntity = this.update(fileEntity);
// 上传文件
try (InputStream inputStream = multipartFile.getInputStream()) {
ossService.writeFileStream(fileEntity.getBucket(), fileEntity.getId(), fileEntity.getName(), inputStream, fileEntity.getSize(), fileEntity.getType());
ossService.writeFileStream(inputStream, fileEntity);
} catch (IOException e) {
throw new BaseException("上传文件失败");
}
@ -105,6 +104,7 @@ public class FileService extends BaseService<FileEntity, FilePageDto> {
throw new BaseException("获取文件HASH失败");
}
fileEntity.setBucket(minioConfig.getBucket());
fileEntity.setCreateTime(new Date());
}
/**
@ -147,7 +147,7 @@ public class FileService extends BaseService<FileEntity, FilePageDto> {
if (fileEntity == null) {
throw new BaseException("文件不存在");
}
ossService.removeFile(fileEntity.getBucket(), fileEntity.getId());
ossService.removeFile(fileEntity);
return super.delete(id);
}
@ -170,6 +170,6 @@ public class FileService extends BaseService<FileEntity, FilePageDto> {
*/
public InputStream fileStreamFindById(String id) {
FileEntity file = this.findById(id);
return ossService.readFileStream(file.getBucket(), file.getId(), file.getName());
return ossService.readFileStream(file);
}
}

View file

@ -1,6 +1,7 @@
package com.bgasol.web.file.file.service;
import com.bgasol.common.core.base.exception.BaseException;
import com.bgasol.model.file.file.entity.FileEntity;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
@ -8,6 +9,7 @@ import io.minio.RemoveObjectArgs;
import io.minio.errors.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -15,6 +17,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
@Service
@RequiredArgsConstructor
@ -23,18 +29,26 @@ import java.security.NoSuchAlgorithmException;
public class OssService {
private final MinioClient minioClient;
private static final String FILE_SEPARATOR = ":";
/**
* 写入文件流到对象存储
*/
public void writeFileStream(String bucket, String id, String name, InputStream inputStream, Long size, String type) {
public void writeFileStream(InputStream inputStream, FileEntity file) {
try {
LocalDate localDate = file.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
String dateStr = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + "/";
String source = file.getSource();
source = ObjectUtils.isEmpty(source) ? "" : source + "/";
// 创建上传文件参数
PutObjectArgs objectArgs = PutObjectArgs
.builder()
.bucket(bucket)
.object(id + ":" + name)
.stream(inputStream, size, -1)
.contentType(type)
.bucket(file.getBucket())
.object(dateStr + source + file.getId() + FILE_SEPARATOR + file.getName())
.stream(inputStream, file.getSize(), -1)
.contentType(file.getType())
.build();
// 上传文件到minio id 相同会覆盖
minioClient.putObject(objectArgs);
@ -50,16 +64,16 @@ public class OssService {
/**
* 从对象存储获取文件流
*
* @param id 文件id
* @return 文件流
*/
@Transactional(readOnly = true)
public InputStream readFileStream(String bucket, String id, String name) {
public InputStream readFileStream(FileEntity file) {
try {
GetObjectArgs build = GetObjectArgs
.builder()
.bucket(bucket)
.object(id + ":" + name)
.bucket(file.getBucket())
.object(buildObjectPath(file))
.build();
// 获取文件流
return minioClient.getObject(build);
@ -71,15 +85,17 @@ public class OssService {
}
}
/**
* 从对象存储中移除文件
*/
public void removeFile(String bucket, String id) {
public void removeFile(FileEntity file) {
try {
RemoveObjectArgs build = RemoveObjectArgs
.builder()
.bucket(bucket)
.object(id)
.bucket(file.getBucket())
.object(buildObjectPath(file))
.build();
minioClient.removeObject(build);
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException |
@ -89,4 +105,18 @@ public class OssService {
throw new BaseException("文件删除失败");
}
}
/**
* 构建对象存储路径
*/
private String buildObjectPath(FileEntity file) {
Date createTime = file.getCreateTime();
LocalDate localDate = createTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
String dateStr = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + "/";
String source = file.getSource();
source = ObjectUtils.isEmpty(source) ? "" : source + "/";
return dateStr + source + file.getId() + FILE_SEPARATOR + file.getName();
}
}

View file

@ -85,7 +85,7 @@ public class ImageService extends BaseService<ImageEntity, ImagePageDto> {
public InputStream imageStreamFindById(String id) {
ImageEntity imageEntity = this.findById(id);
FileEntity file = imageEntity.getFile();
return ossService.readFileStream(file.getBucket(), file.getId(), file.getName());
return ossService.readFileStream(file);
}
/**

View file

@ -53,6 +53,6 @@ public class VideoService extends BaseService<VideoEntity, VideoPageDto> {
public InputStream videoStreamFindById(String id) {
VideoEntity imageEntity = this.findById(id);
FileEntity file = imageEntity.getFile();
return ossService.readFileStream(file.getBucket(), file.getId(), file.getName());
return ossService.readFileStream(file);
}
}