添加文件服务
This commit is contained in:
@@ -1,27 +0,0 @@
|
||||
package com.tashow.cloud.infra.api.config;
|
||||
|
||||
import com.tashow.cloud.common.pojo.CommonResult;
|
||||
import com.tashow.cloud.infra.service.config.ConfigService;
|
||||
import com.tashow.cloud.infraapi.api.config.ConfigApi;
|
||||
import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static com.tashow.cloud.common.pojo.CommonResult.success;
|
||||
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@Validated
|
||||
public class ConfigApiImpl implements ConfigApi {
|
||||
|
||||
@Resource
|
||||
private ConfigService configService;
|
||||
|
||||
@Override
|
||||
public CommonResult<String> getConfigValueByKey(String key) {
|
||||
ConfigDO config = configService.getConfigByKey(key);
|
||||
return success(config != null ? config.getValue() : null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.tashow.cloud.infra.api.file;
|
||||
|
||||
import com.tashow.cloud.common.pojo.CommonResult;
|
||||
import com.tashow.cloud.infra.service.file.FileService;
|
||||
import com.tashow.cloud.infraapi.api.file.FileApi;
|
||||
import com.tashow.cloud.infraapi.api.file.dto.FileCreateReqDTO;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import static com.tashow.cloud.common.pojo.CommonResult.success;
|
||||
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@Validated
|
||||
public class FileApiImpl implements FileApi {
|
||||
|
||||
@Resource
|
||||
private FileService fileService;
|
||||
|
||||
@Override
|
||||
public CommonResult<String> createFile(FileCreateReqDTO createReqDTO) {
|
||||
return success(fileService.createFile(createReqDTO.getName(), createReqDTO.getPath(),
|
||||
createReqDTO.getContent()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
package com.tashow.cloud.infra.controller.admin.codegen;
|
||||
|
||||
import static com.tashow.cloud.common.pojo.CommonResult.success;
|
||||
import static com.tashow.cloud.infra.framework.file.core.utils.FileTypeUtils.writeAttachment;
|
||||
import static com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.ZipUtil;
|
||||
import com.tashow.cloud.common.pojo.CommonResult;
|
||||
@@ -23,14 +19,19 @@ import com.tashow.cloud.infra.service.codegen.CodegenService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import static com.tashow.cloud.common.pojo.CommonResult.success;
|
||||
import static com.tashow.cloud.common.util.io.FileTypeUtils.writeAttachment;
|
||||
import static com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
/** 管理后台 - 代码生成器 */
|
||||
@RestController
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.config;
|
||||
|
||||
import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.tashow.cloud.common.pojo.CommonResult.success;
|
||||
import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
|
||||
import com.tashow.cloud.common.pojo.CommonResult;
|
||||
import com.tashow.cloud.common.pojo.PageParam;
|
||||
import com.tashow.cloud.common.pojo.PageResult;
|
||||
import com.tashow.cloud.excel.excel.core.util.ExcelUtils;
|
||||
import com.tashow.cloud.infra.controller.admin.config.vo.ConfigPageReqVO;
|
||||
import com.tashow.cloud.infra.controller.admin.config.vo.ConfigRespVO;
|
||||
import com.tashow.cloud.infra.controller.admin.config.vo.ConfigSaveReqVO;
|
||||
import com.tashow.cloud.infra.convert.config.ConfigConvert;
|
||||
import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO;
|
||||
import com.tashow.cloud.infra.service.config.ConfigService;
|
||||
import com.tashow.cloud.infraapi.enums.ErrorCodeConstants;
|
||||
import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/** 管理后台 - 参数配置 */
|
||||
@RestController
|
||||
@RequestMapping("/infra/config")
|
||||
@Validated
|
||||
public class ConfigController {
|
||||
|
||||
@Resource private ConfigService configService;
|
||||
|
||||
/** 创建参数配置 */
|
||||
@PostMapping("/create")
|
||||
@PreAuthorize("@ss.hasPermission('infra:config:create')")
|
||||
public CommonResult<Long> createConfig(@Valid @RequestBody ConfigSaveReqVO createReqVO) {
|
||||
return success(configService.createConfig(createReqVO));
|
||||
}
|
||||
|
||||
/** 修改参数配置 */
|
||||
@PutMapping("/update")
|
||||
@PreAuthorize("@ss.hasPermission('infra:config:update')")
|
||||
public CommonResult<Boolean> updateConfig(@Valid @RequestBody ConfigSaveReqVO updateReqVO) {
|
||||
configService.updateConfig(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
/** 删除参数配置 */
|
||||
@DeleteMapping("/delete")
|
||||
@PreAuthorize("@ss.hasPermission('infra:config:delete')")
|
||||
public CommonResult<Boolean> deleteConfig(@RequestParam("id") Long id) {
|
||||
configService.deleteConfig(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
/** 获得参数配置 */
|
||||
@GetMapping(value = "/get")
|
||||
@PreAuthorize("@ss.hasPermission('infra:config:query')")
|
||||
public CommonResult<ConfigRespVO> getConfig(@RequestParam("id") Long id) {
|
||||
return success(ConfigConvert.INSTANCE.convert(configService.getConfig(id)));
|
||||
}
|
||||
|
||||
/** 根据参数键名查询参数值", description = "不可见的配置,不允许返回给前端 */
|
||||
@GetMapping(value = "/get-value-by-key")
|
||||
public CommonResult<String> getConfigKey(@RequestParam("key") String key) {
|
||||
ConfigDO config = configService.getConfigByKey(key);
|
||||
if (config == null) {
|
||||
return success(null);
|
||||
}
|
||||
if (!config.getVisible()) {
|
||||
throw exception(ErrorCodeConstants.CONFIG_GET_VALUE_ERROR_IF_VISIBLE);
|
||||
}
|
||||
return success(config.getValue());
|
||||
}
|
||||
|
||||
/** 获取参数配置分页 */
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("@ss.hasPermission('infra:config:query')")
|
||||
public CommonResult<PageResult<ConfigRespVO>> getConfigPage(@Valid ConfigPageReqVO pageReqVO) {
|
||||
PageResult<ConfigDO> page = configService.getConfigPage(pageReqVO);
|
||||
return success(ConfigConvert.INSTANCE.convertPage(page));
|
||||
}
|
||||
|
||||
/** 导出参数配置 */
|
||||
@GetMapping("/export")
|
||||
@PreAuthorize("@ss.hasPermission('infra:config:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportConfig(ConfigPageReqVO exportReqVO, HttpServletResponse response)
|
||||
throws IOException {
|
||||
exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<ConfigDO> list = configService.getConfigPage(exportReqVO).getList();
|
||||
// 输出
|
||||
ExcelUtils.write(
|
||||
response, "参数配置.xls", "数据", ConfigRespVO.class, ConfigConvert.INSTANCE.convertList(list));
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.config.vo;
|
||||
|
||||
import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
import com.tashow.cloud.common.pojo.PageParam;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/** 管理后台 - 参数配置分页 Request VO */
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class ConfigPageReqVO extends PageParam {
|
||||
|
||||
/** 数据源名称,模糊匹配 */
|
||||
private String name;
|
||||
|
||||
/** 参数键名,模糊匹配 */
|
||||
private String key;
|
||||
|
||||
/** 参数类型,参见 SysConfigTypeEnum 枚举 */
|
||||
private Integer type;
|
||||
|
||||
/** 创建时间 */
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.config.vo;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.tashow.cloud.excel.excel.core.annotations.DictFormat;
|
||||
import com.tashow.cloud.excel.excel.core.convert.DictConvert;
|
||||
import com.tashow.cloud.infraapi.enums.DictTypeConstants;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
|
||||
/** 管理后台 - 参数配置信息 Response VO */
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class ConfigRespVO {
|
||||
|
||||
/** 参数配置序号" */
|
||||
@ExcelProperty("参数配置序号")
|
||||
private Long id;
|
||||
|
||||
/** 参数分类" */
|
||||
@ExcelProperty("参数分类")
|
||||
private String category;
|
||||
|
||||
/** 参数名称", example = "数据库名 */
|
||||
@ExcelProperty("参数名称")
|
||||
private String name;
|
||||
|
||||
/** 参数键名" */
|
||||
@ExcelProperty("参数键名")
|
||||
private String key;
|
||||
|
||||
/** 参数键值" */
|
||||
@ExcelProperty("参数键值")
|
||||
private String value;
|
||||
|
||||
/** 参数类型,参见 SysConfigTypeEnum 枚举" */
|
||||
@ExcelProperty(value = "参数类型", converter = DictConvert.class)
|
||||
@DictFormat(DictTypeConstants.CONFIG_TYPE)
|
||||
private Integer type;
|
||||
|
||||
/** 是否可见" */
|
||||
@ExcelProperty(value = "是否可见", converter = DictConvert.class)
|
||||
@DictFormat(DictTypeConstants.BOOLEAN_STRING)
|
||||
private Boolean visible;
|
||||
|
||||
/** 备注 */
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
/** 创建时间", example = "时间戳格式 */
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.config.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/** 管理后台 - 参数配置创建/修改 Request VO */
|
||||
@Data
|
||||
public class ConfigSaveReqVO {
|
||||
|
||||
/** 参数配置序号 */
|
||||
private Long id;
|
||||
|
||||
/** 参数分组" */
|
||||
@NotEmpty(message = "参数分组不能为空")
|
||||
@Size(max = 50, message = "参数名称不能超过 50 个字符")
|
||||
private String category;
|
||||
|
||||
/** 参数名称", example = "数据库名 */
|
||||
@NotBlank(message = "参数名称不能为空")
|
||||
@Size(max = 100, message = "参数名称不能超过 100 个字符")
|
||||
private String name;
|
||||
|
||||
/** 参数键名" */
|
||||
@NotBlank(message = "参数键名长度不能为空")
|
||||
@Size(max = 100, message = "参数键名长度不能超过 100 个字符")
|
||||
private String key;
|
||||
|
||||
/** 参数键值" */
|
||||
@NotBlank(message = "参数键值不能为空")
|
||||
@Size(max = 500, message = "参数键值长度不能超过 500 个字符")
|
||||
private String value;
|
||||
|
||||
/** 是否可见" */
|
||||
@NotNull(message = "是否可见不能为空")
|
||||
private Boolean visible;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
### 请求 /infra/file-config/create 接口 => 成功
|
||||
POST {{baseUrl}}/infra/file-config/create
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
{
|
||||
"name": "S3 - 七牛云",
|
||||
"remark": "",
|
||||
"storage": 20,
|
||||
"config": {
|
||||
"accessKey": "b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8",
|
||||
"accessSecret": "kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP",
|
||||
"bucket": "ruoyi-vue-pro",
|
||||
"endpoint": "s3-cn-south-1.qiniucs.com",
|
||||
"domain": "http://test.yudao.iocoder.cn",
|
||||
"region": "oss-cn-beijing"
|
||||
}
|
||||
}
|
||||
|
||||
### 请求 /infra/file-config/update 接口 => 成功
|
||||
PUT {{baseUrl}}/infra/file-config/update
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
{
|
||||
"id": 2,
|
||||
"name": "S3 - 七牛云",
|
||||
"remark": "",
|
||||
"config": {
|
||||
"accessKey": "b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8",
|
||||
"accessSecret": "kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP",
|
||||
"bucket": "ruoyi-vue-pro",
|
||||
"endpoint": "s3-cn-south-1.qiniucs.com",
|
||||
"domain": "http://test.yudao.iocoder.cn",
|
||||
"region": "oss-cn-beijing"
|
||||
}
|
||||
}
|
||||
|
||||
### 请求 /infra/file-config/test 接口 => 成功
|
||||
GET {{baseUrl}}/infra/file-config/test?id=2
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
@@ -1,83 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.file;
|
||||
|
||||
import static com.tashow.cloud.common.pojo.CommonResult.success;
|
||||
|
||||
import com.tashow.cloud.common.pojo.CommonResult;
|
||||
import com.tashow.cloud.common.pojo.PageResult;
|
||||
import com.tashow.cloud.common.util.object.BeanUtils;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigRespVO;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigSaveReqVO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO;
|
||||
import com.tashow.cloud.infra.service.file.FileConfigService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/** 管理后台 - 文件配置 */
|
||||
@RestController
|
||||
@RequestMapping("/infra/file-config")
|
||||
@Validated
|
||||
public class FileConfigController {
|
||||
|
||||
@Resource private FileConfigService fileConfigService;
|
||||
|
||||
/** 创建文件配置 */
|
||||
@PostMapping("/create")
|
||||
@PreAuthorize("@ss.hasPermission('infra:file-config:create')")
|
||||
public CommonResult<Long> createFileConfig(@Valid @RequestBody FileConfigSaveReqVO createReqVO) {
|
||||
return success(fileConfigService.createFileConfig(createReqVO));
|
||||
}
|
||||
|
||||
/** 更新文件配置 */
|
||||
@PutMapping("/update")
|
||||
@PreAuthorize("@ss.hasPermission('infra:file-config:update')")
|
||||
public CommonResult<Boolean> updateFileConfig(
|
||||
@Valid @RequestBody FileConfigSaveReqVO updateReqVO) {
|
||||
fileConfigService.updateFileConfig(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
/** 更新文件配置为 Master */
|
||||
@PutMapping("/update-master")
|
||||
@PreAuthorize("@ss.hasPermission('infra:file-config:update')")
|
||||
public CommonResult<Boolean> updateFileConfigMaster(@RequestParam("id") Long id) {
|
||||
fileConfigService.updateFileConfigMaster(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
/** 删除文件配置 */
|
||||
@DeleteMapping("/delete")
|
||||
@PreAuthorize("@ss.hasPermission('infra:file-config:delete')")
|
||||
public CommonResult<Boolean> deleteFileConfig(@RequestParam("id") Long id) {
|
||||
fileConfigService.deleteFileConfig(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
/** 获得文件配置 */
|
||||
@GetMapping("/get")
|
||||
@PreAuthorize("@ss.hasPermission('infra:file-config:query')")
|
||||
public CommonResult<FileConfigRespVO> getFileConfig(@RequestParam("id") Long id) {
|
||||
FileConfigDO config = fileConfigService.getFileConfig(id);
|
||||
return success(BeanUtils.toBean(config, FileConfigRespVO.class));
|
||||
}
|
||||
|
||||
/** 获得文件配置分页 */
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("@ss.hasPermission('infra:file-config:query')")
|
||||
public CommonResult<PageResult<FileConfigRespVO>> getFileConfigPage(
|
||||
@Valid FileConfigPageReqVO pageVO) {
|
||||
PageResult<FileConfigDO> pageResult = fileConfigService.getFileConfigPage(pageVO);
|
||||
return success(BeanUtils.toBean(pageResult, FileConfigRespVO.class));
|
||||
}
|
||||
|
||||
/** 测试文件配置是否正确 */
|
||||
@GetMapping("/test")
|
||||
@PreAuthorize("@ss.hasPermission('infra:file-config:query')")
|
||||
public CommonResult<String> testFileConfig(@RequestParam("id") Long id) throws Exception {
|
||||
String url = fileConfigService.testFileConfig(id);
|
||||
return success(url);
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.file;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import com.tashow.cloud.common.pojo.CommonResult;
|
||||
import com.tashow.cloud.common.pojo.PageResult;
|
||||
import com.tashow.cloud.common.util.object.BeanUtils;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.file.*;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileDO;
|
||||
import com.tashow.cloud.infra.service.file.FileService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import static com.tashow.cloud.common.pojo.CommonResult.success;
|
||||
import static com.tashow.cloud.infra.framework.file.core.utils.FileTypeUtils.writeAttachment;
|
||||
|
||||
/** 管理后台 - 文件存储 */
|
||||
@RestController
|
||||
@RequestMapping("/infra/file")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class FileController {
|
||||
|
||||
@Resource private FileService fileService;
|
||||
|
||||
/** 上传文件", description = "模式一:后端上传文件 */
|
||||
@PostMapping("/upload")
|
||||
public CommonResult<String> uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception {
|
||||
MultipartFile file = uploadReqVO.getFile();
|
||||
String path = uploadReqVO.getPath();
|
||||
return success(
|
||||
fileService.createFile(
|
||||
file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));
|
||||
}
|
||||
|
||||
/** 获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器 */
|
||||
@GetMapping("/presigned-url")
|
||||
public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(@RequestParam("path") String path)
|
||||
throws Exception {
|
||||
return success(fileService.getFilePresignedUrl(path));
|
||||
}
|
||||
|
||||
/** 创建文件", description = "模式二:前端上传文件:配合 presigned-url 接口,记录上传了上传的文件 */
|
||||
@PostMapping("/create")
|
||||
public CommonResult<Long> createFile(@Valid @RequestBody FileCreateReqVO createReqVO) {
|
||||
return success(fileService.createFile(createReqVO));
|
||||
}
|
||||
|
||||
/** 删除文件 */
|
||||
@DeleteMapping("/delete")
|
||||
@PreAuthorize("@ss.hasPermission('infra:file:delete')")
|
||||
public CommonResult<Boolean> deleteFile(@RequestParam("id") Long id) throws Exception {
|
||||
fileService.deleteFile(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
/** 下载文件 */
|
||||
@GetMapping("/{configId}/get/**")
|
||||
@PermitAll
|
||||
public void getFileContent(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
@PathVariable("configId") Long configId)
|
||||
throws Exception {
|
||||
// 获取请求的路径
|
||||
String path = StrUtil.subAfter(request.getRequestURI(), "/get/", false);
|
||||
if (StrUtil.isEmpty(path)) {
|
||||
throw new IllegalArgumentException("结尾的 path 路径必须传递");
|
||||
}
|
||||
// 解码,解决中文路径的问题 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/
|
||||
path = URLUtil.decode(path);
|
||||
|
||||
// 读取内容
|
||||
byte[] content = fileService.getFileContent(configId, path);
|
||||
if (content == null) {
|
||||
log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path);
|
||||
response.setStatus(HttpStatus.NOT_FOUND.value());
|
||||
return;
|
||||
}
|
||||
writeAttachment(response, path, content);
|
||||
}
|
||||
|
||||
/** 获得文件分页 */
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("@ss.hasPermission('infra:file:query')")
|
||||
public CommonResult<PageResult<FileRespVO>> getFilePage(@Valid FilePageReqVO pageVO) {
|
||||
PageResult<FileDO> pageResult = fileService.getFilePage(pageVO);
|
||||
return success(BeanUtils.toBean(pageResult, FileRespVO.class));
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.file.vo.config;
|
||||
|
||||
import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
import com.tashow.cloud.common.pojo.PageParam;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/** 管理后台 - 文件配置分页 Request VO */
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class FileConfigPageReqVO extends PageParam {
|
||||
|
||||
/** 配置名 */
|
||||
private String name;
|
||||
|
||||
/** 存储器 */
|
||||
private Integer storage;
|
||||
|
||||
/** 创建时间 */
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.file.vo.config;
|
||||
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
|
||||
/** 管理后台 - 文件配置 Response VO */
|
||||
@Data
|
||||
public class FileConfigRespVO {
|
||||
|
||||
/** 编号" */
|
||||
private Long id;
|
||||
|
||||
/** 配置名" */
|
||||
private String name;
|
||||
|
||||
/** 存储器,参见 FileStorageEnum 枚举类" */
|
||||
private Integer storage;
|
||||
|
||||
/** 是否为主配置" */
|
||||
private Boolean master;
|
||||
|
||||
/** 存储配置 */
|
||||
private FileClientConfig config;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 创建时间 */
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.file.vo.config;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
|
||||
/** 管理后台 - 文件配置创建/修改 Request VO */
|
||||
@Data
|
||||
public class FileConfigSaveReqVO {
|
||||
|
||||
/** 编号 */
|
||||
private Long id;
|
||||
|
||||
/** 配置名" */
|
||||
@NotNull(message = "配置名不能为空")
|
||||
private String name;
|
||||
|
||||
/** 存储器,参见 FileStorageEnum 枚举类" */
|
||||
@NotNull(message = "存储器不能为空")
|
||||
private Integer storage;
|
||||
|
||||
/** 存储配置,配置是动态参数,所以使用 Map 接收 */
|
||||
@NotNull(message = "存储配置不能为空")
|
||||
private Map<String, Object> config;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.file.vo.file;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/** 管理后台 - 文件创建 Request VO */
|
||||
@Data
|
||||
public class FileCreateReqVO {
|
||||
|
||||
@NotNull(message = "文件配置编号不能为空")
|
||||
/** 文件配置编号" */
|
||||
private Long configId;
|
||||
|
||||
@NotNull(message = "文件路径不能为空")
|
||||
/** 文件路径" */
|
||||
private String path;
|
||||
|
||||
@NotNull(message = "原文件名不能为空")
|
||||
/** 原文件名" */
|
||||
private String name;
|
||||
|
||||
@NotNull(message = "文件 URL不能为空")
|
||||
/** 文件 URL" */
|
||||
private String url;
|
||||
|
||||
/** 文件 MIME 类型 */
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 文件大小", example = "2048
|
||||
*/
|
||||
private Integer size;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.file.vo.file;
|
||||
|
||||
import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
import com.tashow.cloud.common.pojo.PageParam;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/** 管理后台 - 文件分页 Request VO */
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class FilePageReqVO extends PageParam {
|
||||
|
||||
/** 文件路径,模糊匹配 */
|
||||
private String path;
|
||||
|
||||
/** 文件类型,模糊匹配 */
|
||||
private String type;
|
||||
|
||||
/** 创建时间 */
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.file.vo.file;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
/** 管理后台 - 文件预签名地址 Response VO */
|
||||
@Data
|
||||
public class FilePresignedUrlRespVO {
|
||||
|
||||
/** 配置编号" */
|
||||
private Long configId;
|
||||
|
||||
/** 文件上传 URL" */
|
||||
private String uploadUrl;
|
||||
|
||||
/**
|
||||
* 文件访问 URL
|
||||
*
|
||||
* <p>前端上传完文件后,需要使用该 URL 进行访问
|
||||
*/
|
||||
private String url;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.file.vo.file;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
|
||||
/** 管理后台 - 文件 Response VO,不返回 content 字段,太大 */
|
||||
@Data
|
||||
public class FileRespVO {
|
||||
|
||||
/** 文件编号" */
|
||||
private Long id;
|
||||
|
||||
/** 配置编号" */
|
||||
private Long configId;
|
||||
|
||||
/** 文件路径" */
|
||||
private String path;
|
||||
|
||||
/** 原文件名" */
|
||||
private String name;
|
||||
|
||||
/** 文件 URL" */
|
||||
private String url;
|
||||
|
||||
/** 文件MIME类型 */
|
||||
private String type;
|
||||
|
||||
/** 文件大小", example = "2048 */
|
||||
private Integer size;
|
||||
|
||||
/** 创建时间 */
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.admin.file.vo.file;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/** 管理后台 - 上传文件 Request VO */
|
||||
@Data
|
||||
public class FileUploadReqVO {
|
||||
|
||||
/** 文件附件 */
|
||||
@NotNull(message = "文件附件不能为空")
|
||||
private MultipartFile file;
|
||||
|
||||
/** 文件附件 */
|
||||
private String path;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.app.file;
|
||||
|
||||
import static com.tashow.cloud.common.pojo.CommonResult.success;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import com.tashow.cloud.common.pojo.CommonResult;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.file.FileCreateReqVO;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
|
||||
import com.tashow.cloud.infra.controller.app.file.vo.AppFileUploadReqVO;
|
||||
import com.tashow.cloud.infra.service.file.FileService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/** 用户 App - 文件存储 */
|
||||
@RestController
|
||||
@RequestMapping("/infra/file")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class AppFileController {
|
||||
|
||||
@Resource private FileService fileService;
|
||||
|
||||
/** 上传文件 */
|
||||
@PostMapping("/upload")
|
||||
@PermitAll
|
||||
public CommonResult<String> uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception {
|
||||
MultipartFile file = uploadReqVO.getFile();
|
||||
String path = uploadReqVO.getPath();
|
||||
return success(
|
||||
fileService.createFile(
|
||||
file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));
|
||||
}
|
||||
|
||||
/** 获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器 */
|
||||
@GetMapping("/presigned-url")
|
||||
@PermitAll
|
||||
public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(@RequestParam("path") String path)
|
||||
throws Exception {
|
||||
return success(fileService.getFilePresignedUrl(path));
|
||||
}
|
||||
|
||||
/** 创建文件", description = "模式二:前端上传文件:配合 presigned-url 接口,记录上传了上传的文件 */
|
||||
@PostMapping("/create")
|
||||
@PermitAll
|
||||
public CommonResult<Long> createFile(@Valid @RequestBody FileCreateReqVO createReqVO) {
|
||||
return success(fileService.createFile(createReqVO));
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.tashow.cloud.infra.controller.app.file.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/** 用户 App - 上传文件 Request VO */
|
||||
@Data
|
||||
public class AppFileUploadReqVO {
|
||||
|
||||
/** 文件附件 */
|
||||
@NotNull(message = "文件附件不能为空")
|
||||
private MultipartFile file;
|
||||
|
||||
/** 文件附件 */
|
||||
private String path;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.tashow.cloud.infra.convert.config;
|
||||
|
||||
import com.tashow.cloud.common.pojo.PageResult;
|
||||
import com.tashow.cloud.infra.controller.admin.config.vo.ConfigRespVO;
|
||||
import com.tashow.cloud.infra.controller.admin.config.vo.ConfigSaveReqVO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ConfigConvert {
|
||||
|
||||
ConfigConvert INSTANCE = Mappers.getMapper(ConfigConvert.class);
|
||||
|
||||
PageResult<ConfigRespVO> convertPage(PageResult<ConfigDO> page);
|
||||
|
||||
List<ConfigRespVO> convertList(List<ConfigDO> list);
|
||||
|
||||
@Mapping(source = "configKey", target = "key")
|
||||
ConfigRespVO convert(ConfigDO bean);
|
||||
|
||||
@Mapping(source = "key", target = "configKey")
|
||||
ConfigDO convert(ConfigSaveReqVO bean);
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.tashow.cloud.infra.convert.file;
|
||||
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigSaveReqVO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* 文件配置 Convert
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface FileConfigConvert {
|
||||
|
||||
FileConfigConvert INSTANCE = Mappers.getMapper(FileConfigConvert.class);
|
||||
|
||||
@Mapping(target = "config", ignore = true)
|
||||
FileConfigDO convert(FileConfigSaveReqVO bean);
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package com.tashow.cloud.infra.dal.dataobject.config;
|
||||
|
||||
import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO;
|
||||
import com.tashow.cloud.infra.enums.config.ConfigTypeEnum;
|
||||
import com.tashow.cloud.infra.enums.config.ConfigTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.tashow.cloud.infra.enums.config.ConfigTypeEnum;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 参数配置表
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("infra_config")
|
||||
@KeySequence("infra_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class ConfigDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 参数主键
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 参数分类
|
||||
*/
|
||||
private String category;
|
||||
/**
|
||||
* 参数名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 参数键名
|
||||
*
|
||||
* 支持多 DB 类型时,无法直接使用 key + @TableField("config_key") 来实现转换,原因是 "config_key" AS key 而存在报错
|
||||
*/
|
||||
private String configKey;
|
||||
/**
|
||||
* 参数键值
|
||||
*/
|
||||
private String value;
|
||||
/**
|
||||
* 参数类型
|
||||
*
|
||||
* 枚举 {@link ConfigTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
/**
|
||||
* 是否可见
|
||||
*
|
||||
* 不可见的参数,一般是敏感参数,前端不可获取
|
||||
*/
|
||||
private Boolean visible;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package com.tashow.cloud.infra.dal.dataobject.file;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.tashow.cloud.common.util.json.JsonUtils;
|
||||
import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.local.LocalFileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import lombok.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 文件配置表
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "infra_file_config", autoResultMap = true)
|
||||
@KeySequence("infra_file_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class FileConfigDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 配置编号,数据库自增
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 配置名
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 存储器
|
||||
*
|
||||
* 枚举 {@link FileStorageEnum}
|
||||
*/
|
||||
private Integer storage;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
/**
|
||||
* 是否为主配置
|
||||
*
|
||||
* 由于我们可以配置多个文件配置,默认情况下,使用主配置进行文件的上传
|
||||
*/
|
||||
private Boolean master;
|
||||
|
||||
/**
|
||||
* 支付渠道配置
|
||||
*/
|
||||
@TableField(typeHandler = FileClientConfigTypeHandler.class)
|
||||
private FileClientConfig config;
|
||||
|
||||
public static class FileClientConfigTypeHandler extends AbstractJsonTypeHandler<Object> {
|
||||
|
||||
public FileClientConfigTypeHandler(Class<?> type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
public FileClientConfigTypeHandler(Class<?> type, Field field) {
|
||||
super(type, field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parse(String json) {
|
||||
FileClientConfig config = JsonUtils.parseObjectQuietly(json, new TypeReference<>() {});
|
||||
if (config != null) {
|
||||
return config;
|
||||
}
|
||||
|
||||
// 兼容老版本的包路径
|
||||
String className = JsonUtils.parseObject(json, "@class", String.class);
|
||||
className = StrUtil.subAfter(className, ".", true);
|
||||
switch (className) {
|
||||
case "DBFileClientConfig":
|
||||
return JsonUtils.parseObject2(json, DBFileClientConfig.class);
|
||||
case "FtpFileClientConfig":
|
||||
return JsonUtils.parseObject2(json, FtpFileClientConfig.class);
|
||||
case "LocalFileClientConfig":
|
||||
return JsonUtils.parseObject2(json, LocalFileClientConfig.class);
|
||||
case "SftpFileClientConfig":
|
||||
return JsonUtils.parseObject2(json, SftpFileClientConfig.class);
|
||||
case "S3FileClientConfig":
|
||||
return JsonUtils.parseObject2(json, S3FileClientConfig.class);
|
||||
default:
|
||||
throw new IllegalArgumentException("未知的 FileClientConfig 类型:" + json);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJson(Object obj) {
|
||||
return JsonUtils.toJsonString(obj);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.tashow.cloud.infra.dal.dataobject.file;
|
||||
|
||||
import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClient;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 文件内容表
|
||||
*
|
||||
* 专门用于存储 {@link DBFileClient} 的文件内容
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("infra_file_content")
|
||||
@KeySequence("infra_file_content_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class FileContentDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号,数据库自增
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 配置编号
|
||||
*
|
||||
* 关联 {@link FileConfigDO#getId()}
|
||||
*/
|
||||
private Long configId;
|
||||
/**
|
||||
* 路径,即文件名
|
||||
*/
|
||||
private String path;
|
||||
/**
|
||||
* 文件内容
|
||||
*/
|
||||
private byte[] content;
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.tashow.cloud.infra.dal.dataobject.file;
|
||||
|
||||
import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 文件表
|
||||
* 每次文件上传,都会记录一条记录到该表中
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("infra_file")
|
||||
@KeySequence("infra_file_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class FileDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号,数据库自增
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 配置编号
|
||||
*
|
||||
* 关联 {@link FileConfigDO#getId()}
|
||||
*/
|
||||
private Long configId;
|
||||
/**
|
||||
* 原文件名
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 路径,即文件名
|
||||
*/
|
||||
private String path;
|
||||
/**
|
||||
* 访问地址
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* 文件的 MIME 类型,例如 "application/octet-stream"
|
||||
*/
|
||||
private String type;
|
||||
/**
|
||||
* 文件大小
|
||||
*/
|
||||
private Integer size;
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.tashow.cloud.infra.dal.mysql.config;
|
||||
|
||||
import com.tashow.cloud.common.pojo.PageResult;
|
||||
import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX;
|
||||
import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO;
|
||||
import com.tashow.cloud.infra.controller.admin.config.vo.ConfigPageReqVO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ConfigMapper extends BaseMapperX<ConfigDO> {
|
||||
|
||||
default ConfigDO selectByKey(String key) {
|
||||
return selectOne(ConfigDO::getConfigKey, key);
|
||||
}
|
||||
|
||||
default PageResult<ConfigDO> selectPage(ConfigPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<ConfigDO>()
|
||||
.likeIfPresent(ConfigDO::getName, reqVO.getName())
|
||||
.likeIfPresent(ConfigDO::getConfigKey, reqVO.getKey())
|
||||
.eqIfPresent(ConfigDO::getType, reqVO.getType())
|
||||
.betweenIfPresent(ConfigDO::getCreateTime, reqVO.getCreateTime()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.tashow.cloud.infra.dal.mysql.file;
|
||||
|
||||
import com.tashow.cloud.common.pojo.PageResult;
|
||||
import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX;
|
||||
import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface FileConfigMapper extends BaseMapperX<FileConfigDO> {
|
||||
|
||||
default PageResult<FileConfigDO> selectPage(FileConfigPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<FileConfigDO>()
|
||||
.likeIfPresent(FileConfigDO::getName, reqVO.getName())
|
||||
.eqIfPresent(FileConfigDO::getStorage, reqVO.getStorage())
|
||||
.betweenIfPresent(FileConfigDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(FileConfigDO::getId));
|
||||
}
|
||||
|
||||
default FileConfigDO selectByMaster() {
|
||||
return selectOne(FileConfigDO::getMaster, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.tashow.cloud.infra.dal.mysql.file;
|
||||
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileContentDO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileContentDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileContentDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface FileContentMapper extends BaseMapper<FileContentDO> {
|
||||
|
||||
default void deleteByConfigIdAndPath(Long configId, String path) {
|
||||
this.delete(new LambdaQueryWrapper<FileContentDO>()
|
||||
.eq(FileContentDO::getConfigId, configId)
|
||||
.eq(FileContentDO::getPath, path));
|
||||
}
|
||||
|
||||
default List<FileContentDO> selectListByConfigIdAndPath(Long configId, String path) {
|
||||
return selectList(new LambdaQueryWrapper<FileContentDO>()
|
||||
.eq(FileContentDO::getConfigId, configId)
|
||||
.eq(FileContentDO::getPath, path));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.tashow.cloud.infra.dal.mysql.file;
|
||||
|
||||
import com.tashow.cloud.common.pojo.PageResult;
|
||||
import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX;
|
||||
import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileDO;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.file.FilePageReqVO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileDO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 文件操作 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface FileMapper extends BaseMapperX<FileDO> {
|
||||
|
||||
default PageResult<FileDO> selectPage(FilePageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<FileDO>()
|
||||
.likeIfPresent(FileDO::getPath, reqVO.getPath())
|
||||
.likeIfPresent(FileDO::getType, reqVO.getType())
|
||||
.betweenIfPresent(FileDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(FileDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.tashow.cloud.infra.enums.config;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ConfigTypeEnum {
|
||||
|
||||
/**
|
||||
* 系统配置
|
||||
*/
|
||||
SYSTEM(1),
|
||||
/**
|
||||
* 自定义配置
|
||||
*/
|
||||
CUSTOM(2);
|
||||
|
||||
private final Integer type;
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.config;
|
||||
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientFactory;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientFactoryImpl;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientFactory;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientFactoryImpl;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientFactory;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientFactoryImpl;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 文件配置类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class YudaoFileAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public FileClientFactory fileClientFactory() {
|
||||
return new FileClientFactoryImpl();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 文件客户端的抽象类,提供模板方法,减少子类的冗余代码
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractFileClient<Config extends FileClientConfig> implements FileClient {
|
||||
|
||||
/**
|
||||
* 配置编号
|
||||
*/
|
||||
private final Long id;
|
||||
/**
|
||||
* 文件配置
|
||||
*/
|
||||
protected Config config;
|
||||
|
||||
public AbstractFileClient(Long id, Config config) {
|
||||
this.id = id;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
public final void init() {
|
||||
doInit();
|
||||
log.debug("[init][配置({}) 初始化完成]", config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义初始化
|
||||
*/
|
||||
protected abstract void doInit();
|
||||
|
||||
public final void refresh(Config config) {
|
||||
// 判断是否更新
|
||||
if (config.equals(this.config)) {
|
||||
return;
|
||||
}
|
||||
log.info("[refresh][配置({})发生变化,重新初始化]", config);
|
||||
this.config = config;
|
||||
// 初始化
|
||||
this.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件的 URL 访问地址
|
||||
* 使用场景:local、ftp、db,通过 FileController 的 getFile 来获取文件内容
|
||||
*
|
||||
* @param domain 自定义域名
|
||||
* @param path 文件路径
|
||||
* @return URL 访问地址
|
||||
*/
|
||||
protected String formatFileUrl(String domain, String path) {
|
||||
return StrUtil.format("{}/admin-api/infra/file/{}/get/{}", domain, getId(), path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client;
|
||||
|
||||
import com.tashow.cloud.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO;
|
||||
|
||||
/**
|
||||
* 文件客户端
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface FileClient {
|
||||
|
||||
/**
|
||||
* 获得客户端编号
|
||||
*
|
||||
* @return 客户端编号
|
||||
*/
|
||||
Long getId();
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
* @param content 文件流
|
||||
* @param path 相对路径
|
||||
* @return 完整路径,即 HTTP 访问地址
|
||||
* @throws Exception 上传文件时,抛出 Exception 异常
|
||||
*/
|
||||
String upload(byte[] content, String path, String type) throws Exception;
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param path 相对路径
|
||||
* @throws Exception 删除文件时,抛出 Exception 异常
|
||||
*/
|
||||
void delete(String path) throws Exception;
|
||||
|
||||
/**
|
||||
* 获得文件的内容
|
||||
*
|
||||
* @param path 相对路径
|
||||
* @return 文件的内容
|
||||
*/
|
||||
byte[] getContent(String path) throws Exception;
|
||||
|
||||
/**
|
||||
* 获得文件预签名地址
|
||||
*
|
||||
* @param path 相对路径
|
||||
* @return 文件预签名地址
|
||||
*/
|
||||
default FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {
|
||||
throw new UnsupportedOperationException("不支持的操作");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
/**
|
||||
* 文件客户端的配置
|
||||
* 不同实现的客户端,需要不同的配置,通过子类来定义
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
// @JsonTypeInfo 注解的作用,Jackson 多态
|
||||
// 1. 序列化到时数据库时,增加 @class 属性。
|
||||
// 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型
|
||||
public interface FileClientConfig {
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client;
|
||||
|
||||
import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum;
|
||||
import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum;
|
||||
import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum;
|
||||
|
||||
public interface FileClientFactory {
|
||||
|
||||
/**
|
||||
* 获得文件客户端
|
||||
*
|
||||
* @param configId 配置编号
|
||||
* @return 文件客户端
|
||||
*/
|
||||
FileClient getFileClient(Long configId);
|
||||
|
||||
/**
|
||||
* 创建文件客户端
|
||||
*
|
||||
* @param configId 配置编号
|
||||
* @param storage 存储器的枚举 {@link FileStorageEnum}
|
||||
* @param config 文件配置
|
||||
*/
|
||||
<Config extends FileClientConfig> void createOrUpdateFileClient(Long configId, Integer storage, Config config);
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum;
|
||||
import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum;
|
||||
import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* 文件客户端的工厂实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class FileClientFactoryImpl implements FileClientFactory {
|
||||
|
||||
/**
|
||||
* 文件客户端 Map
|
||||
* key:配置编号
|
||||
*/
|
||||
private final ConcurrentMap<Long, AbstractFileClient<?>> clients = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public FileClient getFileClient(Long configId) {
|
||||
AbstractFileClient<?> client = clients.get(configId);
|
||||
if (client == null) {
|
||||
log.error("[getFileClient][配置编号({}) 找不到客户端]", configId);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <Config extends FileClientConfig> void createOrUpdateFileClient(Long configId, Integer storage, Config config) {
|
||||
AbstractFileClient<Config> client = (AbstractFileClient<Config>) clients.get(configId);
|
||||
if (client == null) {
|
||||
client = this.createFileClient(configId, storage, config);
|
||||
client.init();
|
||||
clients.put(client.getId(), client);
|
||||
} else {
|
||||
client.refresh(config);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <Config extends FileClientConfig> AbstractFileClient<Config> createFileClient(
|
||||
Long configId, Integer storage, Config config) {
|
||||
FileStorageEnum storageEnum = FileStorageEnum.getByStorage(storage);
|
||||
Assert.notNull(storageEnum, String.format("文件配置(%s) 为空", storageEnum));
|
||||
// 创建客户端
|
||||
return (AbstractFileClient<Config>) ReflectUtil.newInstance(storageEnum.getClientClass(), configId, config);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client.db;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileContentDO;
|
||||
import com.tashow.cloud.infra.dal.mysql.file.FileContentMapper;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileContentDO;
|
||||
import com.tashow.cloud.infra.dal.mysql.file.FileContentMapper;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.AbstractFileClient;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileContentDO;
|
||||
import com.tashow.cloud.infra.dal.mysql.file.FileContentMapper;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 基于 DB 存储的文件客户端的配置类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class DBFileClient extends AbstractFileClient<DBFileClientConfig> {
|
||||
|
||||
private FileContentMapper fileContentMapper;
|
||||
|
||||
public DBFileClient(Long id, DBFileClientConfig config) {
|
||||
super(id, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
fileContentMapper = SpringUtil.getBean(FileContentMapper.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String upload(byte[] content, String path, String type) {
|
||||
FileContentDO contentDO = new FileContentDO().setConfigId(getId())
|
||||
.setPath(path).setContent(content);
|
||||
fileContentMapper.insert(contentDO);
|
||||
// 拼接返回路径
|
||||
return super.formatFileUrl(config.getDomain(), path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String path) {
|
||||
fileContentMapper.deleteByConfigIdAndPath(getId(), path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent(String path) {
|
||||
List<FileContentDO> list = fileContentMapper.selectListByConfigIdAndPath(getId(), path);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return null;
|
||||
}
|
||||
// 排序后,拿 id 最大的,即最后上传的
|
||||
list.sort(Comparator.comparing(FileContentDO::getId));
|
||||
return CollUtil.getLast(list).getContent();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client.db;
|
||||
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
/**
|
||||
* 基于 DB 存储的文件客户端的配置类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class DBFileClientConfig implements FileClientConfig {
|
||||
|
||||
/**
|
||||
* 自定义域名
|
||||
*/
|
||||
@NotEmpty(message = "domain 不能为空")
|
||||
@URL(message = "domain 必须是 URL 格式")
|
||||
private String domain;
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client.ftp;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.ftp.Ftp;
|
||||
import cn.hutool.extra.ftp.FtpException;
|
||||
import cn.hutool.extra.ftp.FtpMode;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.AbstractFileClient;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
/**
|
||||
* Ftp 文件客户端
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
|
||||
|
||||
private Ftp ftp;
|
||||
|
||||
public FtpFileClient(Long id, FtpFileClientConfig config) {
|
||||
super(id, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
// 把配置的 \ 替换成 /, 如果路径配置 \a\test, 替换成 /a/test, 替换方法已经处理 null 情况
|
||||
config.setBasePath(StrUtil.replace(config.getBasePath(), StrUtil.BACKSLASH, StrUtil.SLASH));
|
||||
// ftp的路径是 / 结尾
|
||||
if (!config.getBasePath().endsWith(StrUtil.SLASH)) {
|
||||
config.setBasePath(config.getBasePath() + StrUtil.SLASH);
|
||||
}
|
||||
// 初始化 Ftp 对象
|
||||
this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
|
||||
CharsetUtil.CHARSET_UTF_8, null, null, FtpMode.valueOf(config.getMode()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String upload(byte[] content, String path, String type) {
|
||||
// 执行写入
|
||||
String filePath = getFilePath(path);
|
||||
String fileName = FileUtil.getName(filePath);
|
||||
String dir = StrUtil.removeSuffix(filePath, fileName);
|
||||
ftp.reconnectIfTimeout();
|
||||
boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content));
|
||||
if (!success) {
|
||||
throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
|
||||
}
|
||||
// 拼接返回路径
|
||||
return super.formatFileUrl(config.getDomain(), path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String path) {
|
||||
String filePath = getFilePath(path);
|
||||
ftp.reconnectIfTimeout();
|
||||
ftp.delFile(filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent(String path) {
|
||||
String filePath = getFilePath(path);
|
||||
String fileName = FileUtil.getName(filePath);
|
||||
String dir = StrUtil.removeSuffix(filePath, fileName);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ftp.reconnectIfTimeout();
|
||||
ftp.download(dir, fileName, out);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private String getFilePath(String path) {
|
||||
return config.getBasePath() + path;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client.ftp;
|
||||
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
/**
|
||||
* Ftp 文件客户端的配置类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class FtpFileClientConfig implements FileClientConfig {
|
||||
|
||||
/**
|
||||
* 基础路径
|
||||
*/
|
||||
@NotEmpty(message = "基础路径不能为空")
|
||||
private String basePath;
|
||||
|
||||
/**
|
||||
* 自定义域名
|
||||
*/
|
||||
@NotEmpty(message = "domain 不能为空")
|
||||
@URL(message = "domain 必须是 URL 格式")
|
||||
private String domain;
|
||||
|
||||
/**
|
||||
* 主机地址
|
||||
*/
|
||||
@NotEmpty(message = "host 不能为空")
|
||||
private String host;
|
||||
/**
|
||||
* 主机端口
|
||||
*/
|
||||
@NotNull(message = "port 不能为空")
|
||||
private Integer port;
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotEmpty(message = "用户名不能为空")
|
||||
private String username;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
private String password;
|
||||
/**
|
||||
* 连接模式
|
||||
*
|
||||
* 使用 {@link cn.hutool.extra.ftp.FtpMode} 对应的字符串
|
||||
*/
|
||||
@NotEmpty(message = "连接模式不能为空")
|
||||
private String mode;
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client.local;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.AbstractFileClient;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 本地文件客户端
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class LocalFileClient extends AbstractFileClient<LocalFileClientConfig> {
|
||||
|
||||
public LocalFileClient(Long id, LocalFileClientConfig config) {
|
||||
super(id, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
// 补全风格。例如说 Linux 是 /,Windows 是 \
|
||||
if (!config.getBasePath().endsWith(File.separator)) {
|
||||
config.setBasePath(config.getBasePath() + File.separator);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String upload(byte[] content, String path, String type) {
|
||||
// 执行写入
|
||||
String filePath = getFilePath(path);
|
||||
FileUtil.writeBytes(content, filePath);
|
||||
// 拼接返回路径
|
||||
return super.formatFileUrl("", path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String path) {
|
||||
String filePath = getFilePath(path);
|
||||
FileUtil.del(filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent(String path) {
|
||||
String filePath = getFilePath(path);
|
||||
return FileUtil.readBytes(filePath);
|
||||
}
|
||||
|
||||
private String getFilePath(String path) {
|
||||
return config.getBasePath() + path;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client.local;
|
||||
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
/**
|
||||
* 本地文件客户端的配置类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class LocalFileClientConfig implements FileClientConfig {
|
||||
|
||||
/**
|
||||
* 基础路径
|
||||
*/
|
||||
@NotEmpty(message = "基础路径不能为空")
|
||||
private String basePath;
|
||||
|
||||
/**
|
||||
* 自定义域名
|
||||
*/
|
||||
@NotEmpty(message = "domain 不能为空")
|
||||
@URL(message = "domain 必须是 URL 格式")
|
||||
private String domain;
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client.s3;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 文件预签名地址 Response DTO
|
||||
*
|
||||
* @author owen
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class FilePresignedUrlRespDTO {
|
||||
|
||||
/**
|
||||
* 文件上传 URL(用于上传)
|
||||
*
|
||||
* 例如说:
|
||||
*/
|
||||
private String uploadUrl;
|
||||
|
||||
/**
|
||||
* 文件 URL(用于读取、下载等)
|
||||
*/
|
||||
private String url;
|
||||
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client.s3;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.AbstractFileClient;
|
||||
import com.amazonaws.HttpMethod;
|
||||
import com.amazonaws.auth.AWSStaticCredentialsProvider;
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
import com.amazonaws.client.builder.AwsClientBuilder;
|
||||
import com.amazonaws.services.s3.AmazonS3Client;
|
||||
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
|
||||
import com.amazonaws.services.s3.model.ObjectMetadata;
|
||||
import com.amazonaws.services.s3.model.S3Object;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
|
||||
* <p>
|
||||
* S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
|
||||
|
||||
private AmazonS3Client client;
|
||||
|
||||
public S3FileClient(Long id, S3FileClientConfig config) {
|
||||
super(id, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
// 补全 domain
|
||||
if (StrUtil.isEmpty(config.getDomain())) {
|
||||
config.setDomain(buildDomain());
|
||||
}
|
||||
// 初始化客户端
|
||||
client = (AmazonS3Client)AmazonS3ClientBuilder.standard()
|
||||
.withCredentials(buildCredentials())
|
||||
.withEndpointConfiguration(buildEndpointConfiguration())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 config 秘钥,构建 S3 客户端的认证信息
|
||||
*
|
||||
* @return S3 客户端的认证信息
|
||||
*/
|
||||
private AWSStaticCredentialsProvider buildCredentials() {
|
||||
return new AWSStaticCredentialsProvider(
|
||||
new BasicAWSCredentials(config.getAccessKey(), config.getAccessSecret()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 S3 客户端的 Endpoint 配置,包括 region、endpoint
|
||||
*
|
||||
* @return S3 客户端的 EndpointConfiguration 配置
|
||||
*/
|
||||
private AwsClientBuilder.EndpointConfiguration buildEndpointConfiguration() {
|
||||
return new AwsClientBuilder.EndpointConfiguration(config.getEndpoint(),
|
||||
null); // 无需设置 region
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 bucket + endpoint 构建访问的 Domain 地址
|
||||
*
|
||||
* @return Domain 地址
|
||||
*/
|
||||
private String buildDomain() {
|
||||
// 如果已经是 http 或者 https,则不进行拼接.主要适配 MinIO
|
||||
if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
|
||||
return StrUtil.format("{}/{}", config.getEndpoint(), config.getBucket());
|
||||
}
|
||||
// 阿里云、腾讯云、华为云都适合。七牛云比较特殊,必须有自定义域名
|
||||
return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String upload(byte[] content, String path, String type) throws Exception {
|
||||
// 元数据,主要用于设置文件类型
|
||||
ObjectMetadata objectMetadata = new ObjectMetadata();
|
||||
objectMetadata.setContentType(type);
|
||||
objectMetadata.setContentLength(content.length); // 如果不设置,会有 “ No content length specified for stream data” 警告日志
|
||||
// 执行上传
|
||||
client.putObject(config.getBucket(),
|
||||
path, // 相对路径
|
||||
new ByteArrayInputStream(content), // 文件内容
|
||||
objectMetadata);
|
||||
|
||||
// 拼接返回路径
|
||||
return config.getDomain() + "/" + path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String path) throws Exception {
|
||||
client.deleteObject(config.getBucket(), path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent(String path) throws Exception {
|
||||
S3Object tempS3Object = client.getObject(config.getBucket(), path);
|
||||
return IoUtil.readBytes(tempS3Object.getObjectContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {
|
||||
// 设定过期时间为 10 分钟。取值范围:1 秒 ~ 7 天
|
||||
Date expiration = new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10));
|
||||
// 生成上传 URL
|
||||
String uploadUrl = String.valueOf(client.generatePresignedUrl(config.getBucket(), path, expiration , HttpMethod.PUT));
|
||||
return new FilePresignedUrlRespDTO(uploadUrl, config.getDomain() + "/" + path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client.s3;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import jakarta.validation.constraints.AssertTrue;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
/**
|
||||
* S3 文件客户端的配置类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class S3FileClientConfig implements FileClientConfig {
|
||||
|
||||
public static final String ENDPOINT_QINIU = "qiniucs.com";
|
||||
public static final String ENDPOINT_ALIYUN = "aliyuncs.com";
|
||||
public static final String ENDPOINT_TENCENT = "myqcloud.com";
|
||||
public static final String ENDPOINT_VOLCES = "volces.com"; // 火山云(字节)
|
||||
|
||||
/**
|
||||
* 节点地址
|
||||
* 1. MinIO:https://www.iocoder.cn/Spring-Boot/MinIO 。例如说,http://127.0.0.1:9000
|
||||
* 2. 阿里云:https://help.aliyun.com/document_detail/31837.html
|
||||
* 3. 腾讯云:https://cloud.tencent.com/document/product/436/6224
|
||||
* 4. 七牛云:https://developer.qiniu.com/kodo/4088/s3-access-domainname
|
||||
* 5. 华为云:https://console.huaweicloud.com/apiexplorer/#/endpoint/OBS
|
||||
* 6. 火山云:https://www.volcengine.com/docs/6349/107356
|
||||
*/
|
||||
@NotNull(message = "endpoint 不能为空")
|
||||
private String endpoint;
|
||||
/**
|
||||
* 自定义域名
|
||||
* 1. MinIO:通过 Nginx 配置
|
||||
* 2. 阿里云:https://help.aliyun.com/document_detail/31836.html
|
||||
* 3. 腾讯云:https://cloud.tencent.com/document/product/436/11142
|
||||
* 4. 七牛云:https://developer.qiniu.com/kodo/8556/set-the-custom-source-domain-name
|
||||
* 5. 华为云:https://support.huaweicloud.com/usermanual-obs/obs_03_0032.html
|
||||
* 6. 火山云:https://www.volcengine.com/docs/6349/128983
|
||||
*/
|
||||
@URL(message = "domain 必须是 URL 格式")
|
||||
private String domain;
|
||||
/**
|
||||
* 存储 Bucket
|
||||
*/
|
||||
@NotNull(message = "bucket 不能为空")
|
||||
private String bucket;
|
||||
|
||||
/**
|
||||
* 访问 Key
|
||||
* 1. MinIO:https://www.iocoder.cn/Spring-Boot/MinIO
|
||||
* 2. 阿里云:https://ram.console.aliyun.com/manage/ak
|
||||
* 3. 腾讯云:https://console.cloud.tencent.com/cam/capi
|
||||
* 4. 七牛云:https://portal.qiniu.com/user/key
|
||||
* 5. 华为云:https://support.huaweicloud.com/qs-obs/obs_qs_0005.html
|
||||
* 6. 火山云:https://console.volcengine.com/iam/keymanage/
|
||||
*/
|
||||
@NotNull(message = "accessKey 不能为空")
|
||||
private String accessKey;
|
||||
/**
|
||||
* 访问 Secret
|
||||
*/
|
||||
@NotNull(message = "accessSecret 不能为空")
|
||||
private String accessSecret;
|
||||
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
@AssertTrue(message = "domain 不能为空")
|
||||
@JsonIgnore
|
||||
public boolean isDomainValid() {
|
||||
// 如果是七牛,必须带有 domain
|
||||
if (StrUtil.contains(endpoint, ENDPOINT_QINIU) && StrUtil.isEmpty(domain)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client.sftp;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.extra.ssh.Sftp;
|
||||
import com.tashow.cloud.common.util.io.FileUtils;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.AbstractFileClient;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Sftp 文件客户端
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class SftpFileClient extends AbstractFileClient<SftpFileClientConfig> {
|
||||
|
||||
private Sftp sftp;
|
||||
|
||||
public SftpFileClient(Long id, SftpFileClientConfig config) {
|
||||
super(id, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
// 补全风格。例如说 Linux 是 /,Windows 是 \
|
||||
if (!config.getBasePath().endsWith(File.separator)) {
|
||||
config.setBasePath(config.getBasePath() + File.separator);
|
||||
}
|
||||
// 初始化 Ftp 对象
|
||||
this.sftp = new Sftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String upload(byte[] content, String path, String type) {
|
||||
// 执行写入
|
||||
String filePath = getFilePath(path);
|
||||
File file = FileUtils.createTempFile(content);
|
||||
sftp.upload(filePath, file);
|
||||
// 拼接返回路径
|
||||
return super.formatFileUrl(config.getDomain(), path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String path) {
|
||||
String filePath = getFilePath(path);
|
||||
sftp.delFile(filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent(String path) {
|
||||
String filePath = getFilePath(path);
|
||||
File destFile = FileUtils.createTempFile();
|
||||
sftp.download(filePath, destFile);
|
||||
return FileUtil.readBytes(destFile);
|
||||
}
|
||||
|
||||
private String getFilePath(String path) {
|
||||
return config.getBasePath() + path;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.client.sftp;
|
||||
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
/**
|
||||
* Sftp 文件客户端的配置类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class SftpFileClientConfig implements FileClientConfig {
|
||||
|
||||
/**
|
||||
* 基础路径
|
||||
*/
|
||||
@NotEmpty(message = "基础路径不能为空")
|
||||
private String basePath;
|
||||
|
||||
/**
|
||||
* 自定义域名
|
||||
*/
|
||||
@NotEmpty(message = "domain 不能为空")
|
||||
@URL(message = "domain 必须是 URL 格式")
|
||||
private String domain;
|
||||
|
||||
/**
|
||||
* 主机地址
|
||||
*/
|
||||
@NotEmpty(message = "host 不能为空")
|
||||
private String host;
|
||||
/**
|
||||
* 主机端口
|
||||
*/
|
||||
@NotNull(message = "port 不能为空")
|
||||
private Integer port;
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotEmpty(message = "用户名不能为空")
|
||||
private String username;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
private String password;
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.enums;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.local.LocalFileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.local.LocalFileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.local.LocalFileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.local.LocalFileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClientConfig;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 文件存储器枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum FileStorageEnum {
|
||||
|
||||
DB(1, DBFileClientConfig.class, DBFileClient.class),
|
||||
|
||||
LOCAL(10, LocalFileClientConfig.class, LocalFileClient.class),
|
||||
FTP(11, FtpFileClientConfig.class, FtpFileClient.class),
|
||||
SFTP(12, SftpFileClientConfig.class, SftpFileClient.class),
|
||||
|
||||
S3(20, S3FileClientConfig.class, S3FileClient.class),
|
||||
;
|
||||
|
||||
/**
|
||||
* 存储器
|
||||
*/
|
||||
private final Integer storage;
|
||||
|
||||
/**
|
||||
* 配置类
|
||||
*/
|
||||
private final Class<? extends FileClientConfig> configClass;
|
||||
/**
|
||||
* 客户端类
|
||||
*/
|
||||
private final Class<? extends FileClient> clientClass;
|
||||
|
||||
public static FileStorageEnum getByStorage(Integer storage) {
|
||||
return ArrayUtil.firstMatch(o -> o.getStorage().equals(storage), values());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package com.tashow.cloud.infra.framework.file.core.utils;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.tika.Tika;
|
||||
import org.jaudiotagger.audio.AudioFile;
|
||||
import org.jaudiotagger.audio.AudioFileIO;
|
||||
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||
import org.jaudiotagger.audio.exceptions.CannotWriteException;
|
||||
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
|
||||
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||
import org.jaudiotagger.tag.Tag;
|
||||
import org.jaudiotagger.tag.TagException;
|
||||
import org.jaudiotagger.tag.id3.AbstractID3v2Tag;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
/**
|
||||
* 文件类型 Utils
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class FileTypeUtils {
|
||||
|
||||
private static final ThreadLocal<Tika> TIKA = TransmittableThreadLocal.withInitial(Tika::new);
|
||||
|
||||
/**
|
||||
* 获得文件的 mineType,对于doc,jar等文件会有误差
|
||||
*
|
||||
* @param data 文件内容
|
||||
* @return mineType 无法识别时会返回“application/octet-stream”
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static String getMineType(byte[] data) {
|
||||
return TIKA.get().detect(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 已知文件名,获取文件类型,在某些情况下比通过字节数组准确,例如使用jar文件时,通过名字更为准确
|
||||
*
|
||||
* @param name 文件名
|
||||
* @return mineType 无法识别时会返回“application/octet-stream”
|
||||
*/
|
||||
public static String getMineType(String name) {
|
||||
return TIKA.get().detect(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在拥有文件和数据的情况下,最好使用此方法,最为准确
|
||||
*
|
||||
* @param data 文件内容
|
||||
* @param name 文件名
|
||||
* @return mineType 无法识别时会返回“application/octet-stream”
|
||||
*/
|
||||
public static String getMineType(byte[] data, String name) {
|
||||
return TIKA.get().detect(data, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回附件
|
||||
*
|
||||
* @param response 响应
|
||||
* @param filename 文件名
|
||||
* @param content 附件内容
|
||||
*/
|
||||
public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {
|
||||
// 设置 header 和 contentType
|
||||
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
|
||||
String contentType = getMineType(content, filename);
|
||||
response.setContentType(contentType);
|
||||
// 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题
|
||||
if (StrUtil.containsIgnoreCase(contentType, "video")||StrUtil.containsIgnoreCase(contentType, "audio")) {
|
||||
response.setHeader("Content-Length", String.valueOf(content.length - 1));
|
||||
response.setHeader("Content-Range", String.valueOf(content.length - 1));
|
||||
response.setHeader("Accept-Ranges", "bytes");
|
||||
}
|
||||
// 输出附件
|
||||
IoUtil.write(response.getOutputStream(), false, content);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws CannotReadException, TagException, InvalidAudioFrameException, ReadOnlyFileException, IOException, CannotWriteException, URISyntaxException {
|
||||
URL url = new URL("https://petshy.tashowz.com/admin-api/infra/file/29/get/jna2-雪球-难过焦虑.wav");
|
||||
File file = new File(url.getFile());
|
||||
System.out.println(file.exists());
|
||||
AudioFile audioFile = AudioFileIO.read(file);
|
||||
Tag tag = audioFile.getTag();
|
||||
if (tag instanceof AbstractID3v2Tag) {
|
||||
AbstractID3v2Tag id3v2Tag = (AbstractID3v2Tag) tag;
|
||||
// id3v2Tag.delete(); // 删除所有ID3标签
|
||||
} else {
|
||||
System.out.println("The file does not contain ID3v2 tags.");
|
||||
}
|
||||
AudioFileIO.write(audioFile); // 保存更改
|
||||
System.out.println("ID3 tags removed successfully.");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* 文件客户端,支持多种存储器
|
||||
*
|
||||
* 1. local:本地磁盘
|
||||
* 2. ftp:FTP 服务器
|
||||
* 3. sftp:SFTP 服务器
|
||||
* 4. db:数据库
|
||||
* 5. s3:支持 S3 协议的云存储服务,例如说 MinIO、阿里云、华为云、腾讯云、七牛云等等
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
package com.tashow.cloud.infra.framework.file;
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.tashow.cloud.infra.service.config;
|
||||
|
||||
import com.tashow.cloud.common.pojo.PageResult;
|
||||
import com.tashow.cloud.infra.controller.admin.config.vo.ConfigPageReqVO;
|
||||
import com.tashow.cloud.infra.controller.admin.config.vo.ConfigSaveReqVO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* 参数配置 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface ConfigService {
|
||||
|
||||
/**
|
||||
* 创建参数配置
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 配置编号
|
||||
*/
|
||||
Long createConfig(@Valid ConfigSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新参数配置
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateConfig(@Valid ConfigSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除参数配置
|
||||
*
|
||||
* @param id 配置编号
|
||||
*/
|
||||
void deleteConfig(Long id);
|
||||
|
||||
/**
|
||||
* 获得参数配置
|
||||
*
|
||||
* @param id 配置编号
|
||||
* @return 参数配置
|
||||
*/
|
||||
ConfigDO getConfig(Long id);
|
||||
|
||||
/**
|
||||
* 根据参数键,获得参数配置
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return 参数配置
|
||||
*/
|
||||
ConfigDO getConfigByKey(String key);
|
||||
|
||||
/**
|
||||
* 获得参数配置分页列表
|
||||
*
|
||||
* @param reqVO 分页条件
|
||||
* @return 分页列表
|
||||
*/
|
||||
PageResult<ConfigDO> getConfigPage(ConfigPageReqVO reqVO);
|
||||
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package com.tashow.cloud.infra.service.config;
|
||||
|
||||
import com.tashow.cloud.common.pojo.PageResult;
|
||||
import com.tashow.cloud.infra.convert.config.ConfigConvert;
|
||||
import com.tashow.cloud.infra.enums.config.ConfigTypeEnum;
|
||||
import com.tashow.cloud.infra.controller.admin.config.vo.ConfigPageReqVO;
|
||||
import com.tashow.cloud.infra.controller.admin.config.vo.ConfigSaveReqVO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO;
|
||||
import com.tashow.cloud.infra.dal.mysql.config.ConfigMapper;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.tashow.cloud.infraapi.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 参数配置 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@Validated
|
||||
public class ConfigServiceImpl implements ConfigService {
|
||||
|
||||
@Resource
|
||||
private ConfigMapper configMapper;
|
||||
|
||||
@Override
|
||||
public Long createConfig(ConfigSaveReqVO createReqVO) {
|
||||
// 校验参数配置 key 的唯一性
|
||||
validateConfigKeyUnique(null, createReqVO.getKey());
|
||||
|
||||
// 插入参数配置
|
||||
ConfigDO config = ConfigConvert.INSTANCE.convert(createReqVO);
|
||||
config.setType(ConfigTypeEnum.CUSTOM.getType());
|
||||
configMapper.insert(config);
|
||||
return config.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateConfig(ConfigSaveReqVO updateReqVO) {
|
||||
// 校验自己存在
|
||||
validateConfigExists(updateReqVO.getId());
|
||||
// 校验参数配置 key 的唯一性
|
||||
validateConfigKeyUnique(updateReqVO.getId(), updateReqVO.getKey());
|
||||
|
||||
// 更新参数配置
|
||||
ConfigDO updateObj = ConfigConvert.INSTANCE.convert(updateReqVO);
|
||||
configMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteConfig(Long id) {
|
||||
// 校验配置存在
|
||||
ConfigDO config = validateConfigExists(id);
|
||||
// 内置配置,不允许删除
|
||||
if (ConfigTypeEnum.SYSTEM.getType().equals(config.getType())) {
|
||||
throw exception(CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE);
|
||||
}
|
||||
// 删除
|
||||
configMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigDO getConfig(Long id) {
|
||||
return configMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigDO getConfigByKey(String key) {
|
||||
return configMapper.selectByKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<ConfigDO> getConfigPage(ConfigPageReqVO pageReqVO) {
|
||||
return configMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public ConfigDO validateConfigExists(Long id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
ConfigDO config = configMapper.selectById(id);
|
||||
if (config == null) {
|
||||
throw exception(CONFIG_NOT_EXISTS);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void validateConfigKeyUnique(Long id, String key) {
|
||||
ConfigDO config = configMapper.selectByKey(key);
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
// 如果 id 为空,说明不用比较是否为相同 id 的参数配置
|
||||
if (id == null) {
|
||||
throw exception(CONFIG_KEY_DUPLICATE);
|
||||
}
|
||||
if (!config.getId().equals(id)) {
|
||||
throw exception(CONFIG_KEY_DUPLICATE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package com.tashow.cloud.infra.service.file;
|
||||
|
||||
import com.tashow.cloud.common.pojo.PageResult;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigSaveReqVO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO;
|
||||
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClient;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* 文件配置 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface FileConfigService {
|
||||
|
||||
/**
|
||||
* 创建文件配置
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createFileConfig(@Valid FileConfigSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新文件配置
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateFileConfig(@Valid FileConfigSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 更新文件配置为 Master
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void updateFileConfigMaster(Long id);
|
||||
|
||||
/**
|
||||
* 删除文件配置
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteFileConfig(Long id);
|
||||
|
||||
/**
|
||||
* 获得文件配置
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 文件配置
|
||||
*/
|
||||
FileConfigDO getFileConfig(Long id);
|
||||
|
||||
/**
|
||||
* 获得文件配置分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 文件配置分页
|
||||
*/
|
||||
PageResult<FileConfigDO> getFileConfigPage(FileConfigPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 测试文件配置是否正确,通过上传文件
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 文件 URL
|
||||
*/
|
||||
String testFileConfig(Long id) throws Exception;
|
||||
|
||||
/**
|
||||
* 获得指定编号的文件客户端
|
||||
*
|
||||
* @param id 配置编号
|
||||
* @return 文件客户端
|
||||
*/
|
||||
FileClient getFileClient(Long id);
|
||||
|
||||
/**
|
||||
* 获得 Master 文件客户端
|
||||
*
|
||||
* @return 文件客户端
|
||||
*/
|
||||
FileClient getMasterFileClient();
|
||||
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
package com.tashow.cloud.infra.service.file;
|
||||
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.tashow.cloud.common.pojo.PageResult;
|
||||
import com.tashow.cloud.common.util.json.JsonUtils;
|
||||
import com.tashow.cloud.common.util.validation.ValidationUtils;
|
||||
import com.tashow.cloud.infra.convert.file.FileConfigConvert;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigSaveReqVO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO;
|
||||
import com.tashow.cloud.infra.dal.mysql.file.FileConfigMapper;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClientFactory;
|
||||
import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Validator;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.tashow.cloud.common.util.cache.CacheUtils.buildAsyncReloadingCache;
|
||||
import static com.tashow.cloud.infraapi.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER;
|
||||
import static com.tashow.cloud.infraapi.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* 文件配置 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class FileConfigServiceImpl implements FileConfigService {
|
||||
|
||||
private static final Long CACHE_MASTER_ID = 0L;
|
||||
|
||||
/**
|
||||
* {@link FileClient} 缓存,通过它异步刷新 fileClientFactory
|
||||
*/
|
||||
@Getter
|
||||
private final LoadingCache<Long, FileClient> clientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
|
||||
new CacheLoader<Long, FileClient>() {
|
||||
|
||||
@Override
|
||||
public FileClient load(Long id) {
|
||||
FileConfigDO config = Objects.equals(CACHE_MASTER_ID, id) ?
|
||||
fileConfigMapper.selectByMaster() : fileConfigMapper.selectById(id);
|
||||
if (config != null) {
|
||||
fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
|
||||
}
|
||||
return fileClientFactory.getFileClient(null == config ? id : config.getId());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@Resource
|
||||
private FileClientFactory fileClientFactory;
|
||||
|
||||
@Resource
|
||||
private FileConfigMapper fileConfigMapper;
|
||||
|
||||
@Resource
|
||||
private Validator validator;
|
||||
|
||||
@Override
|
||||
public Long createFileConfig(FileConfigSaveReqVO createReqVO) {
|
||||
FileConfigDO fileConfig = FileConfigConvert.INSTANCE.convert(createReqVO)
|
||||
.setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig()))
|
||||
.setMaster(false); // 默认非 master
|
||||
fileConfigMapper.insert(fileConfig);
|
||||
return fileConfig.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFileConfig(FileConfigSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
FileConfigDO config = validateFileConfigExists(updateReqVO.getId());
|
||||
// 更新
|
||||
FileConfigDO updateObj = FileConfigConvert.INSTANCE.convert(updateReqVO)
|
||||
.setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig()));
|
||||
fileConfigMapper.updateById(updateObj);
|
||||
|
||||
// 清空缓存
|
||||
clearCache(config.getId(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateFileConfigMaster(Long id) {
|
||||
// 校验存在
|
||||
validateFileConfigExists(id);
|
||||
// 更新其它为非 master
|
||||
fileConfigMapper.updateBatch(new FileConfigDO().setMaster(false));
|
||||
// 更新
|
||||
fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true));
|
||||
|
||||
// 清空缓存
|
||||
clearCache(null, true);
|
||||
}
|
||||
|
||||
private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) {
|
||||
// 获取配置类
|
||||
Class<? extends FileClientConfig> configClass = FileStorageEnum.getByStorage(storage)
|
||||
.getConfigClass();
|
||||
FileClientConfig clientConfig = JsonUtils.parseObject2(JsonUtils.toJsonString(config), configClass);
|
||||
// 参数校验
|
||||
ValidationUtils.validate(validator, clientConfig);
|
||||
// 设置参数
|
||||
return clientConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFileConfig(Long id) {
|
||||
// 校验存在
|
||||
FileConfigDO config = validateFileConfigExists(id);
|
||||
if (Boolean.TRUE.equals(config.getMaster())) {
|
||||
throw exception(FILE_CONFIG_DELETE_FAIL_MASTER);
|
||||
}
|
||||
// 删除
|
||||
fileConfigMapper.deleteById(id);
|
||||
|
||||
// 清空缓存
|
||||
clearCache(id, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空指定文件配置
|
||||
*
|
||||
* @param id 配置编号
|
||||
* @param master 是否主配置
|
||||
*/
|
||||
private void clearCache(Long id, Boolean master) {
|
||||
if (id != null) {
|
||||
clientCache.invalidate(id);
|
||||
}
|
||||
if (Boolean.TRUE.equals(master)) {
|
||||
clientCache.invalidate(CACHE_MASTER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
private FileConfigDO validateFileConfigExists(Long id) {
|
||||
FileConfigDO config = fileConfigMapper.selectById(id);
|
||||
if (config == null) {
|
||||
throw exception(FILE_CONFIG_NOT_EXISTS);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileConfigDO getFileConfig(Long id) {
|
||||
return fileConfigMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<FileConfigDO> getFileConfigPage(FileConfigPageReqVO pageReqVO) {
|
||||
return fileConfigMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String testFileConfig(Long id) throws Exception {
|
||||
// 校验存在
|
||||
validateFileConfigExists(id);
|
||||
// 上传文件
|
||||
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
|
||||
return getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileClient getFileClient(Long id) {
|
||||
return clientCache.getUnchecked(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileClient getMasterFileClient() {
|
||||
return clientCache.getUnchecked(CACHE_MASTER_ID);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package com.tashow.cloud.infra.service.file;
|
||||
|
||||
import com.tashow.cloud.common.pojo.PageResult;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.file.FileCreateReqVO;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.file.FilePageReqVO;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileDO;
|
||||
|
||||
/**
|
||||
* 文件 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface FileService {
|
||||
|
||||
/**
|
||||
* 获得文件分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 文件分页
|
||||
*/
|
||||
PageResult<FileDO> getFilePage(FilePageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 保存文件,并返回文件的访问路径
|
||||
*
|
||||
* @param name 文件名称
|
||||
* @param path 文件路径
|
||||
* @param content 文件内容
|
||||
* @return 文件路径
|
||||
*/
|
||||
String createFile(String name, String path, byte[] content);
|
||||
|
||||
/**
|
||||
* 创建文件
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createFile(FileCreateReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteFile(Long id) throws Exception;
|
||||
|
||||
/**
|
||||
* 获得文件内容
|
||||
*
|
||||
* @param configId 配置编号
|
||||
* @param path 文件路径
|
||||
* @return 文件内容
|
||||
*/
|
||||
byte[] getFileContent(Long configId, String path) throws Exception;
|
||||
|
||||
/**
|
||||
* 生成文件预签名地址信息
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @return 预签名地址信息
|
||||
*/
|
||||
FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception;
|
||||
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package com.tashow.cloud.infra.service.file;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.tashow.cloud.common.pojo.PageResult;
|
||||
import com.tashow.cloud.common.util.io.FileUtils;
|
||||
import com.tashow.cloud.common.util.object.BeanUtils;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.file.FileCreateReqVO;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.file.FilePageReqVO;
|
||||
import com.tashow.cloud.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
|
||||
import com.tashow.cloud.infra.dal.dataobject.file.FileDO;
|
||||
import com.tashow.cloud.infra.dal.mysql.file.FileMapper;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.FileClient;
|
||||
import com.tashow.cloud.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO;
|
||||
import com.tashow.cloud.infra.framework.file.core.utils.FileTypeUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.tashow.cloud.infraapi.enums.ErrorCodeConstants.FILE_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* 文件 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
public class FileServiceImpl implements FileService {
|
||||
|
||||
@Resource
|
||||
private FileConfigService fileConfigService;
|
||||
|
||||
@Resource
|
||||
private FileMapper fileMapper;
|
||||
|
||||
@Override
|
||||
public PageResult<FileDO> getFilePage(FilePageReqVO pageReqVO) {
|
||||
return fileMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String createFile(String name, String path, byte[] content) {
|
||||
// 计算默认的 path 名
|
||||
String type = FileTypeUtils.getMineType(content, name);
|
||||
if (StrUtil.isEmpty(path)) {
|
||||
path = FileUtils.generatePath(content, name);
|
||||
}
|
||||
// 如果 name 为空,则使用 path 填充
|
||||
if (StrUtil.isEmpty(name)) {
|
||||
name = path;
|
||||
}
|
||||
|
||||
// 上传到文件存储器
|
||||
FileClient client = fileConfigService.getMasterFileClient();
|
||||
Assert.notNull(client, "客户端(master) 不能为空");
|
||||
String url = client.upload(content, path, type);
|
||||
|
||||
// 保存到数据库
|
||||
FileDO file = new FileDO();
|
||||
file.setConfigId(client.getId());
|
||||
file.setName(name);
|
||||
file.setPath(path);
|
||||
file.setUrl(url);
|
||||
file.setType(type);
|
||||
file.setSize(content.length);
|
||||
fileMapper.insert(file);
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createFile(FileCreateReqVO createReqVO) {
|
||||
FileDO file = BeanUtils.toBean(createReqVO, FileDO.class);
|
||||
fileMapper.insert(file);
|
||||
return file.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFile(Long id) throws Exception {
|
||||
// 校验存在
|
||||
FileDO file = validateFileExists(id);
|
||||
|
||||
// 从文件存储器中删除
|
||||
FileClient client = fileConfigService.getFileClient(file.getConfigId());
|
||||
Assert.notNull(client, "客户端({}) 不能为空", file.getConfigId());
|
||||
client.delete(file.getPath());
|
||||
|
||||
// 删除记录
|
||||
fileMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private FileDO validateFileExists(Long id) {
|
||||
FileDO fileDO = fileMapper.selectById(id);
|
||||
if (fileDO == null) {
|
||||
throw exception(FILE_NOT_EXISTS);
|
||||
}
|
||||
return fileDO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getFileContent(Long configId, String path) throws Exception {
|
||||
FileClient client = fileConfigService.getFileClient(configId);
|
||||
Assert.notNull(client, "客户端({}) 不能为空", configId);
|
||||
return client.getContent(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception {
|
||||
FileClient fileClient = fileConfigService.getMasterFileClient();
|
||||
FilePresignedUrlRespDTO presignedObjectUrl = fileClient.getPresignedObjectUrl(path);
|
||||
return BeanUtils.toBean(presignedObjectUrl, FilePresignedUrlRespVO.class,
|
||||
object -> object.setConfigId(fileClient.getId()));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user