feat: 新增部分存储库管理后端 API

This commit is contained in:
Charles7c 2023-12-26 22:58:29 +08:00
parent ed8bb57fe2
commit 5a2c5be088
18 changed files with 981 additions and 15 deletions

View File

@ -82,6 +82,12 @@
<artifactId>sms4j-spring-boot-starter</artifactId>
</dependency>
<!-- X File Storage一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS...等其它兼容 S3 协议的存储平台) -->
<dependency>
<groupId>org.dromara.x-file-storage</groupId>
<artifactId>x-file-storage-spring</artifactId>
</dependency>
<!-- FreeMarker模板引擎 -->
<dependency>
<groupId>org.freemarker</groupId>

View File

@ -61,11 +61,11 @@ public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler {
Long createUser = LoginHelper.getUserId();
LocalDateTime createTime = LocalDateTime.now();
if (metaObject.getOriginalObject() instanceof BaseDO baseDO) {
// 继承了 BaseDO 的类填充创建信息
// 继承了 BaseDO 的类填充创建信息字段
baseDO.setCreateUser(ObjectUtil.defaultIfNull(baseDO.getCreateUser(), createUser));
baseDO.setCreateTime(ObjectUtil.defaultIfNull(baseDO.getCreateTime(), createTime));
} else {
// 未继承 BaseDO 的类如存在创建信息则进行填充
// 未继承 BaseDO 的类如存在创建信息字段则进行填充
this.fillFieldValue(metaObject, CREATE_USER, createUser, false);
this.fillFieldValue(metaObject, CREATE_TIME, createTime, false);
}
@ -94,7 +94,7 @@ public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler {
baseDO.setUpdateUser(updateUser);
baseDO.setUpdateTime(updateTime);
} else {
// 未继承 BaseDO 的类根据类中拥有的修改信息进行填充不存在修改信息不进行填充
// 未继承 BaseDO 的类根据类中拥有的修改信息字段进行填充不存在修改信息字段不进行填充
this.fillFieldValue(metaObject, UPDATE_USER, updateUser, true);
this.fillFieldValue(metaObject, UPDATE_TIME, updateTime, true);
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.system.config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.recorder.FileRecorder;
import org.springframework.stereotype.Component;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.EscapeUtil;
import top.charles7c.continew.admin.common.util.helper.LoginHelper;
import top.charles7c.continew.admin.system.enums.FileTypeEnum;
import top.charles7c.continew.admin.system.mapper.FileMapper;
import top.charles7c.continew.admin.system.mapper.StorageMapper;
import top.charles7c.continew.admin.system.model.entity.FileDO;
import top.charles7c.continew.admin.system.model.entity.StorageDO;
/**
* 文件记录实现类
*
* @author Charles7c
* @since 2023/12/24 22:31
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class FileRecorderImpl implements FileRecorder {
private final FileMapper fileMapper;
private final StorageMapper storageMapper;
@Override
public boolean save(FileInfo fileInfo) {
FileDO file = new FileDO();
file.setName(EscapeUtil.unescape(fileInfo.getOriginalFilename()));
file.setSize(fileInfo.getSize());
file.setUrl(fileInfo.getUrl());
file.setExtension(fileInfo.getExt());
file.setType(FileTypeEnum.getByExtension(file.getExtension()));
StorageDO storage = storageMapper.lambdaQuery().eq(StorageDO::getCode, fileInfo.getPlatform()).one();
file.setStorageId(storage.getId());
file.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime()));
file.setUpdateUser(LoginHelper.getUserId());
file.setUpdateTime(file.getCreateTime());
fileMapper.insert(file);
return true;
}
@Override
public FileInfo getByUrl(String url) {
FileDO file = fileMapper.lambdaQuery().eq(FileDO::getUrl, url).one();
FileInfo fileInfo = new FileInfo();
fileInfo.setOriginalFilename(file.getName());
fileInfo.setSize(file.getSize());
fileInfo.setUrl(file.getUrl());
fileInfo.setExt(file.getExtension());
return fileInfo;
}
@Override
public boolean delete(String url) {
return fileMapper.lambdaUpdate().eq(FileDO::getUrl, url).remove();
}
}

View File

@ -16,12 +16,15 @@
package top.charles7c.continew.admin.system.enums;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import cn.hutool.core.util.StrUtil;
import top.charles7c.continew.starter.data.mybatis.plus.base.IBaseEnum;
/**
@ -62,4 +65,17 @@ public enum FileTypeEnum implements IBaseEnum<Integer> {
private final Integer value;
private final String description;
private final List<String> extensions;
/**
* 根据扩展名查询
*
* @param extension
* 扩展名
* @return 文件类型
*/
public static FileTypeEnum getByExtension(String extension) {
return Arrays.stream(FileTypeEnum.values())
.filter(t -> t.getExtensions().contains(StrUtil.emptyIfNull(extension).toLowerCase())).findFirst()
.orElse(FileTypeEnum.UNKNOWN);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.system.mapper;
import top.charles7c.continew.admin.system.model.entity.StorageDO;
import top.charles7c.continew.starter.data.mybatis.plus.base.BaseMapper;
/**
* 存储库 Mapper
*
* @author Charles7c
* @since 2023/12/26 22:09
*/
public interface StorageMapper extends BaseMapper<StorageDO> {}

View File

@ -20,7 +20,7 @@ import java.io.Serial;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.*;
import top.charles7c.continew.admin.system.enums.FileTypeEnum;
import top.charles7c.continew.starter.extension.crud.base.BaseDO;
@ -38,6 +38,9 @@ public class FileDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 名称
*/
@ -58,18 +61,13 @@ public class FileDO extends BaseDO {
*/
private String extension;
/**
* MIME类型
*/
private String mimeType;
/**
* 类型
*/
private FileTypeEnum type;
/**
* 存储库ID
* 存储库 ID
*/
private Long storageId;
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.system.model.entity;
import java.io.Serial;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.charles7c.continew.starter.extension.crud.base.BaseDO;
/**
* 存储库实体
*
* @author Charles7c
* @since 2023/12/26 22:09
*/
@Data
@TableName("sys_storage")
public class StorageDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 名称
*/
private String name;
/**
* 编码
*/
private String code;
/**
* Access Key
*/
private String accessKey;
/**
* Secret Key
*/
private String secretKey;
/**
* Endpoint
*/
private String endpoint;
/**
* 桶名称
*/
private String bucketName;
/**
* 自定义域名
*/
private String domain;
/**
* 描述
*/
private String description;
/**
* 是否为默认存储
*/
private Boolean isDefault;
/**
* 排序
*/
private Integer sort;
/**
* 状态
*/
private Integer status;
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.system.model.query;
import java.io.Serial;
import java.io.Serializable;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.charles7c.continew.starter.data.mybatis.plus.query.Query;
import top.charles7c.continew.starter.data.mybatis.plus.query.QueryType;
/**
* 存储库查询条件
*
* @author Charles7c
* @since 2023/12/26 22:09
*/
@Data
@Schema(description = "存储库查询条件")
public class StorageQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 名称
*/
@Schema(description = "名称")
@Query(type = QueryType.INNER_LIKE)
private String name;
/**
* 状态
*/
@Schema(description = "状态")
@Query(type = QueryType.EQUAL)
private Integer status;
}

View File

@ -0,0 +1,111 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.system.model.req;
import java.io.Serial;
import jakarta.validation.constraints.*;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.charles7c.continew.admin.common.enums.DisEnableStatusEnum;
import top.charles7c.continew.starter.extension.crud.base.BaseReq;
/**
* 创建或修改存储库信息
*
* @author Charles7c
* @since 2023/12/26 22:09
*/
@Data
@Schema(description = "创建或修改存储库信息")
public class StorageReq extends BaseReq {
@Serial
private static final long serialVersionUID = 1L;
/**
* 名称
*/
@Schema(description = "名称")
@NotBlank(message = "名称不能为空")
private String name;
/**
* 编码
*/
@Schema(description = "编码")
@NotBlank(message = "编码不能为空")
private String code;
/**
* Access Key
*/
@Schema(description = "Access Key")
private String accessKey;
/**
* Secret Key
*/
@Schema(description = "Secret Key")
private String secretKey;
/**
* Endpoint
*/
@Schema(description = "Endpoint")
private String endpoint;
/**
* 桶名称
*/
@Schema(description = "桶名称")
private String bucketName;
/**
* 自定义域名
*/
@Schema(description = "自定义域名")
private String domain;
/**
* 描述
*/
@Schema(description = "描述")
private String description;
/**
* 是否为默认存储
*/
@Schema(description = "是否为默认存储")
@NotNull(message = "是否为默认存储不能为空")
private Boolean isDefault;
/**
* 排序
*/
@Schema(description = "排序")
private Integer sort;
/**
* 状态
*/
@Schema(description = "状态1启用2禁用", type = "Integer", allowableValues = {"1", "2"}, example = "1")
private DisEnableStatusEnum status;
}

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.system.model.resp;
import java.io.Serial;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import top.charles7c.continew.starter.extension.crud.base.BaseDetailResp;
/**
* 存储库详情信息
*
* @author Charles7c
* @since 2023/12/26 22:09
*/
@Data
@ExcelIgnoreUnannotated
@Schema(description = "存储库详情信息")
public class StorageDetailResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 名称
*/
@Schema(description = "名称")
@ExcelProperty(value = "名称")
private String name;
/**
* 编码
*/
@Schema(description = "编码")
@ExcelProperty(value = "编码")
private String code;
/**
* Access Key
*/
@Schema(description = "Access Key")
@ExcelProperty(value = "Access Key")
private String accessKey;
/**
* Secret Key
*/
@Schema(description = "Secret Key")
@ExcelProperty(value = "Secret Key")
private String secretKey;
/**
* Endpoint
*/
@Schema(description = "Endpoint")
@ExcelProperty(value = "Endpoint")
private String endpoint;
/**
* 桶名称
*/
@Schema(description = "桶名称")
@ExcelProperty(value = "桶名称")
private String bucketName;
/**
* 自定义域名
*/
@Schema(description = "自定义域名")
@ExcelProperty(value = "自定义域名")
private String domain;
/**
* 描述
*/
@Schema(description = "描述")
@ExcelProperty(value = "描述")
private String description;
/**
* 是否为默认存储
*/
@Schema(description = "是否为默认存储")
@ExcelProperty(value = "是否为默认存储")
private Boolean isDefault;
/**
* 排序
*/
@Schema(description = "排序")
@ExcelProperty(value = "排序")
private Integer sort;
/**
* 状态
*/
@Schema(description = "状态")
@ExcelProperty(value = "状态")
private Integer status;
}

View File

@ -0,0 +1,105 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.system.model.resp;
import java.io.Serial;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.charles7c.continew.starter.extension.crud.base.BaseResp;
/**
* 存储库信息
*
* @author Charles7c
* @since 2023/12/26 22:09
*/
@Data
@Schema(description = "存储库信息")
public class StorageResp extends BaseResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 名称
*/
@Schema(description = "名称")
private String name;
/**
* 编码
*/
@Schema(description = "编码")
private String code;
/**
* Access Key
*/
@Schema(description = "Access Key")
private String accessKey;
/**
* Secret Key
*/
@Schema(description = "Secret Key")
private String secretKey;
/**
* Endpoint
*/
@Schema(description = "Endpoint")
private String endpoint;
/**
* 桶名称
*/
@Schema(description = "桶名称")
private String bucketName;
/**
* 自定义域名
*/
@Schema(description = "自定义域名")
private String domain;
/**
* 描述
*/
@Schema(description = "描述")
private String description;
/**
* 是否为默认存储
*/
@Schema(description = "是否为默认存储")
private Boolean isDefault;
/**
* 排序
*/
@Schema(description = "排序")
private Integer sort;
/**
* 状态
*/
@Schema(description = "状态")
private Integer status;
}

View File

@ -16,6 +16,10 @@
package top.charles7c.continew.admin.system.service;
import java.util.List;
import org.springframework.web.multipart.MultipartFile;
import top.charles7c.continew.admin.system.model.query.FileQuery;
import top.charles7c.continew.admin.system.model.req.FileReq;
import top.charles7c.continew.admin.system.model.resp.FileDetailResp;
@ -28,4 +32,24 @@ import top.charles7c.continew.starter.extension.crud.base.BaseService;
* @author Charles7c
* @since 2023/12/23 10:38
*/
public interface FileService extends BaseService<FileResp, FileDetailResp, FileQuery, FileReq> {}
public interface FileService extends BaseService<FileResp, FileDetailResp, FileQuery, FileReq> {
/**
* 上传
*
* @param file
* 文件信息
* @param storageCode
* 存储库编码
*/
void upload(MultipartFile file, String storageCode);
/**
* 根据存储库 ID 列表查询
*
* @param storageIds
* 存储库 ID 列表
* @return 文件数量
*/
Long countByStorageIds(List<Long> storageIds);
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.system.service;
import top.charles7c.continew.admin.system.model.entity.StorageDO;
import top.charles7c.continew.admin.system.model.query.StorageQuery;
import top.charles7c.continew.admin.system.model.req.StorageReq;
import top.charles7c.continew.admin.system.model.resp.StorageDetailResp;
import top.charles7c.continew.admin.system.model.resp.StorageResp;
import top.charles7c.continew.starter.extension.crud.base.BaseService;
/**
* 存储库业务接口
*
* @author Charles7c
* @since 2023/12/26 22:09
*/
public interface StorageService extends BaseService<StorageResp, StorageDetailResp, StorageQuery, StorageReq> {
/**
* 查询默认存储库
*
* @return 存储库信息
*/
StorageDO getDefaultStorage();
/**
* 根据编码查询
*
* @param code
* 编码
* @return 存储库信息
*/
StorageDO getByCode(String code);
}

View File

@ -16,17 +16,29 @@
package top.charles7c.continew.admin.system.service.impl;
import lombok.RequiredArgsConstructor;
import java.util.List;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.x.file.storage.core.*;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import cn.hutool.core.util.StrUtil;
import top.charles7c.continew.admin.system.mapper.FileMapper;
import top.charles7c.continew.admin.system.model.entity.FileDO;
import top.charles7c.continew.admin.system.model.entity.StorageDO;
import top.charles7c.continew.admin.system.model.query.FileQuery;
import top.charles7c.continew.admin.system.model.req.FileReq;
import top.charles7c.continew.admin.system.model.resp.FileDetailResp;
import top.charles7c.continew.admin.system.model.resp.FileResp;
import top.charles7c.continew.admin.system.service.FileService;
import top.charles7c.continew.admin.system.service.StorageService;
import top.charles7c.continew.starter.core.util.validate.CheckUtils;
import top.charles7c.continew.starter.extension.crud.base.BaseServiceImpl;
/**
@ -35,7 +47,45 @@ import top.charles7c.continew.starter.extension.crud.base.BaseServiceImpl;
* @author Charles7c
* @since 2023/12/23 10:38
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileResp, FileDetailResp, FileQuery, FileReq>
implements FileService {}
implements FileService {
@Resource
private StorageService storageService;
private final FileStorageService fileStorageService;
@Override
public void upload(MultipartFile file, String storageCode) {
if (StrUtil.isBlank(storageCode)) {
StorageDO storage = storageService.getDefaultStorage();
CheckUtils.throwIfNull(storage, "请先指定默认存储库");
storageCode = storage.getCode();
}
UploadPretreatment uploadPretreatment = fileStorageService.of(file).setPlatform(storageCode);
uploadPretreatment.setProgressMonitor(new ProgressListener() {
@Override
public void start() {
log.info("开始上传");
}
@Override
public void progress(long progressSize, long allSize) {
log.info("已上传 [{}],总大小 [{}]", progressSize, allSize);
}
@Override
public void finish() {
log.info("上传结束");
}
});
uploadPretreatment.upload();
}
@Override
public Long countByStorageIds(List<Long> storageIds) {
return baseMapper.lambdaQuery().in(FileDO::getStorageId, storageIds).count();
}
}

View File

@ -0,0 +1,185 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.system.service.impl;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletContext;
import lombok.RequiredArgsConstructor;
import org.dromara.x.file.storage.core.FileStorageProperties;
import org.dromara.x.file.storage.core.FileStorageService;
import org.dromara.x.file.storage.core.FileStorageServiceBuilder;
import org.dromara.x.file.storage.core.platform.FileStorage;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.util.UrlPathHelper;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import top.charles7c.continew.admin.common.enums.DisEnableStatusEnum;
import top.charles7c.continew.admin.system.mapper.StorageMapper;
import top.charles7c.continew.admin.system.model.entity.StorageDO;
import top.charles7c.continew.admin.system.model.query.StorageQuery;
import top.charles7c.continew.admin.system.model.req.StorageReq;
import top.charles7c.continew.admin.system.model.resp.StorageDetailResp;
import top.charles7c.continew.admin.system.model.resp.StorageResp;
import top.charles7c.continew.admin.system.service.FileService;
import top.charles7c.continew.admin.system.service.StorageService;
import top.charles7c.continew.starter.core.util.validate.CheckUtils;
import top.charles7c.continew.starter.extension.crud.base.BaseServiceImpl;
/**
* 存储库业务实现
*
* @author Charles7c
* @since 2023/12/26 22:09
*/
@Service
@RequiredArgsConstructor
public class StorageServiceImpl
extends BaseServiceImpl<StorageMapper, StorageDO, StorageResp, StorageDetailResp, StorageQuery, StorageReq>
implements StorageService {
private final ApplicationContext applicationContext;
private final FileStorageService fileStorageService;
@Resource
private final FileService fileService;
@Override
public Long add(StorageReq req) {
String code = req.getCode();
CheckUtils.throwIf(this.isCodeExists(code, null), "新增失败,[{}] 已存在", code);
req.setStatus(DisEnableStatusEnum.ENABLE);
this.loadFileStorage(req);
return super.add(req);
}
@Override
public void update(StorageReq req, Long id) {
String code = req.getCode();
CheckUtils.throwIf(this.isCodeExists(code, id), "修改失败,[{}] 已存在", code);
StorageDO oldStorage = super.getById(id);
CheckUtils.throwIf(
Boolean.TRUE.equals(oldStorage.getIsDefault()) && DisEnableStatusEnum.DISABLE.equals(req.getStatus()),
"[{}] 是默认存储库,不允许禁用", oldStorage.getName());
this.loadFileStorage(req);
super.update(req, id);
}
@Override
public void delete(List<Long> ids) {
CheckUtils.throwIf(fileService.countByStorageIds(ids) > 0, "所选存储库存在文件关联,请删除文件后重试");
List<StorageDO> storageList = baseMapper.lambdaQuery().in(StorageDO::getId, ids).list();
storageList.forEach(s -> this.removeFileStorage(s.getCode()));
super.delete(ids);
}
@Override
public StorageDO getDefaultStorage() {
return baseMapper.lambdaQuery().eq(StorageDO::getIsDefault, true).one();
}
@Override
public StorageDO getByCode(String code) {
return baseMapper.lambdaQuery().eq(StorageDO::getCode, code).one();
}
/**
* 加载存储配置
*
* @param req
* 存储配置信息
*/
private void loadFileStorage(StorageReq req) {
CopyOnWriteArrayList<FileStorage> fileStorageList = fileStorageService.getFileStorageList();
FileStorageProperties.LocalPlusConfig localPlusConfig = new FileStorageProperties.LocalPlusConfig();
localPlusConfig.setPlatform(req.getCode());
localPlusConfig.setStoragePath(req.getBucketName());
localPlusConfig.setDomain(req.getDomain());
fileStorageList
.addAll(FileStorageServiceBuilder.buildLocalPlusFileStorage(Collections.singletonList(localPlusConfig)));
}
/**
* 移除存储配置
*
* @param code
* 存储配置编码
*/
private void removeFileStorage(String code) {
CopyOnWriteArrayList<FileStorage> fileStorageList = fileStorageService.getFileStorageList();
FileStorage fileStorage = fileStorageService.getFileStorage(code);
fileStorageList.remove(fileStorage);
fileStorage.close();
}
/**
* 编码是否存在
*
* @param code
* 编码
* @param id
* ID
* @return 是否存在
*/
private boolean isCodeExists(String code, Long id) {
return baseMapper.lambdaQuery().eq(StorageDO::getCode, code).ne(null != id, StorageDO::getId, id).exists();
}
/**
* 注册静态资源映射
*
* @param registerMapping
* 静态资源映射列表
*/
private void registerResource(Map<String, String> registerMapping) {
final UrlPathHelper urlPathHelper = applicationContext.getBean("mvcUrlPathHelper", UrlPathHelper.class);
final ContentNegotiationManager contentNegotiationManager =
applicationContext.getBean("mvcContentNegotiationManager", ContentNegotiationManager.class);
final ServletContext servletContext = applicationContext.getBean(ServletContext.class);
final HandlerMapping resourceHandlerMapping =
applicationContext.getBean("resourceHandlerMapping", HandlerMapping.class);
// 已经注册的静态资源映射
final Map<String, Object> handlerMap =
(Map<String, Object>)ReflectUtil.getFieldValue(resourceHandlerMapping, "handlerMap");
final ResourceHandlerRegistry resourceHandlerRegistry =
new ResourceHandlerRegistry(applicationContext, servletContext, contentNegotiationManager, urlPathHelper);
// 重新注册静态资源映射
for (Map.Entry<String, String> entry : registerMapping.entrySet()) {
String pathPattern = StrUtil.appendIfMissing(entry.getKey(), "/**");
String resourceLocations = StrUtil.appendIfMissing(entry.getValue(), "/");
// 移除之前注册过的
handlerMap.remove(pathPattern);
// 重新注册
resourceHandlerRegistry.addResourceHandler(pathPattern).addResourceLocations("file:" + resourceLocations);
}
final Map<String, ?> additionalUrlMap =
ReflectUtil.<SimpleUrlHandlerMapping>invoke(resourceHandlerRegistry, "getHandlerMapping").getUrlMap();
ReflectUtil.<Void>invoke(resourceHandlerMapping, "registerHandlers", additionalUrlMap);
}
}

View File

@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
import io.swagger.v3.oas.annotations.Hidden;
import org.dromara.x.file.storage.spring.EnableFileStorage;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
@ -44,6 +45,7 @@ import top.charles7c.continew.starter.core.autoconfigure.project.ProjectProperti
* @since 2022/12/8 23:15
*/
@Slf4j
@EnableFileStorage
@RestController
@SpringBootApplication
@RequiredArgsConstructor

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.webapi.system;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import top.charles7c.continew.admin.system.model.query.StorageQuery;
import top.charles7c.continew.admin.system.model.req.StorageReq;
import top.charles7c.continew.admin.system.model.resp.StorageDetailResp;
import top.charles7c.continew.admin.system.model.resp.StorageResp;
import top.charles7c.continew.admin.system.service.StorageService;
import top.charles7c.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.charles7c.continew.starter.extension.crud.base.BaseController;
/**
* 存储库管理 API
*
* @author Charles7c
* @since 2023/12/26 22:09
*/
@Tag(name = "存储库管理 API")
@RestController
@CrudRequestMapping("/system/storage")
public class StorageController
extends BaseController<StorageService, StorageResp, StorageDetailResp, StorageQuery, StorageReq> {}

View File

@ -12,8 +12,8 @@ CREATE TABLE IF NOT EXISTS `sys_file` (
`storage_id` bigint(20) NOT NULL COMMENT '存储库ID',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
`update_time` datetime NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_type`(`type`) USING BTREE,
INDEX `idx_create_user`(`create_user`) USING BTREE,