diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java index df84836c..97440057 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java @@ -24,7 +24,6 @@ import me.zhengjie.exception.BadRequestException; import org.apache.poi.util.IOUtils; import org.apache.poi.xssf.streaming.SXSSFSheet; import org.springframework.web.multipart.MultipartFile; - import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -136,15 +135,15 @@ public class FileUtil extends cn.hutool.core.io.FileUtil { String resultSize; if (size / GB >= 1) { //如果当前Byte的值大于等于1GB - resultSize = DF.format(size / (float) GB) + "GB "; + resultSize = DF.format(size / (float) GB) + "GB"; } else if (size / MB >= 1) { //如果当前Byte的值大于等于1MB - resultSize = DF.format(size / (float) MB) + "MB "; + resultSize = DF.format(size / (float) MB) + "MB"; } else if (size / KB >= 1) { //如果当前Byte的值大于等于1KB - resultSize = DF.format(size / (float) KB) + "KB "; + resultSize = DF.format(size / (float) KB) + "KB"; } else { - resultSize = size + "B "; + resultSize = size + "B"; } return resultSize; } diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java index 21480065..806431cc 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java @@ -16,6 +16,7 @@ package me.zhengjie.utils; import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.slf4j.Logger; @@ -234,7 +235,10 @@ public class RedisUtils { if (value == null) { return null; } - if (clazz.isInstance(value)) { + // 如果 value 不是目标类型,则尝试将其反序列化为 clazz 类型 + if (!clazz.isInstance(value)) { + return JSON.parseObject(value.toString(), clazz); + } else if (clazz.isInstance(value)) { return clazz.cast(value); } else { return null; diff --git a/eladmin-logging/src/main/java/me/zhengjie/service/impl/SysLogServiceImpl.java b/eladmin-logging/src/main/java/me/zhengjie/service/impl/SysLogServiceImpl.java index bcff3710..455f7b86 100644 --- a/eladmin-logging/src/main/java/me/zhengjie/service/impl/SysLogServiceImpl.java +++ b/eladmin-logging/src/main/java/me/zhengjie/service/impl/SysLogServiceImpl.java @@ -18,6 +18,7 @@ package me.zhengjie.service.impl; import cn.hutool.core.lang.Dict; import cn.hutool.core.util.ObjectUtil; import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import lombok.RequiredArgsConstructor; import me.zhengjie.domain.SysLog; @@ -136,7 +137,14 @@ public class SysLogServiceImpl implements SysLogService { // 将RequestBody注解修饰的参数作为请求参数 RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); if (requestBody != null) { - params.putAll((JSONObject) JSON.toJSON(args[i])); + // [el-async-1] ERROR o.s.a.i.SimpleAsyncUncaughtExceptionHandler - Unexpected exception occurred invoking async method: public void me.zhengjie.service.impl.SysLogServiceImpl.save(java.lang.String,java.lang.String,java.lang.String,org.aspectj.lang.ProceedingJoinPoint,me.zhengjie.domain.SysLog) + // java.lang.ClassCastException: com.alibaba.fastjson2.JSONArray cannot be cast to com.alibaba.fastjson2.JSONObject + Object json = JSON.toJSON(args[i]); + if (json instanceof JSONArray) { + params.put("reqBodyList", json); + } else { + params.putAll((JSONObject) json); + } } else { String key = parameters[i].getName(); params.put(key, args[i]); diff --git a/eladmin-system/src/main/java/me/zhengjie/AppRun.java b/eladmin-system/src/main/java/me/zhengjie/AppRun.java index a8dfb89a..696b1520 100644 --- a/eladmin-system/src/main/java/me/zhengjie/AppRun.java +++ b/eladmin-system/src/main/java/me/zhengjie/AppRun.java @@ -22,6 +22,7 @@ import me.zhengjie.utils.SpringBeanHolder; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.ApplicationPidFileWriter; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.http.HttpStatus; @@ -50,10 +51,11 @@ public class AppRun { // 监控应用的PID,启动时可指定PID路径:--spring.pid.file=/home/eladmin/app.pid // 或者在 application.yml 添加文件路径,方便 kill,kill `cat /home/eladmin/app.pid` springApplication.addListeners(new ApplicationPidFileWriter()); - springApplication.run(args); + ConfigurableApplicationContext context = springApplication.run(args); + String port = context.getEnvironment().getProperty("server.port"); log.info("---------------------------------------------"); - log.info("Local: {}", "http://localhost:8000"); - log.info("Swagger: {}", "http://localhost:8000/doc.html"); + log.info("Local: http://localhost:{}", port); + log.info("Swagger: http://localhost:{}/doc.html", port); log.info("---------------------------------------------"); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/AppServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/AppServiceImpl.java index d7aeb296..9fee6182 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/AppServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/AppServiceImpl.java @@ -56,7 +56,7 @@ public class AppServiceImpl implements AppService { @Override public AppDto findById(Long id) { - App app = appRepository.findById(id).orElseGet(App::new); + App app = appRepository.findById(id).orElseGet(App::new); ValidationUtil.isNull(app.getId(),"App","id",id); return appMapper.toDto(app); } @@ -64,6 +64,11 @@ public class AppServiceImpl implements AppService { @Override @Transactional(rollbackFor = Exception.class) public void create(App resources) { + // 验证应用名称是否存在恶意攻击payload,https://github.com/elunez/eladmin/issues/873 + String appName = resources.getName(); + if (appName.contains(";") || appName.contains("|") || appName.contains("&")) { + throw new IllegalArgumentException("非法的应用名称,请勿包含[; | &]等特殊字符"); + } verification(resources); appRepository.save(resources); } @@ -71,6 +76,11 @@ public class AppServiceImpl implements AppService { @Override @Transactional(rollbackFor = Exception.class) public void update(App resources) { + // 验证应用名称是否存在恶意攻击payload,https://github.com/elunez/eladmin/issues/873 + String appName = resources.getName(); + if (appName.contains(";") || appName.contains("|") || appName.contains("&")) { + throw new IllegalArgumentException("非法的应用名称,请勿包含[; | &]等特殊字符"); + } verification(resources); App app = appRepository.findById(resources.getId()).orElseGet(App::new); ValidationUtil.isNull(app.getId(),"App","id",resources.getId()); diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DeployServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DeployServiceImpl.java index 41a25988..350cff2e 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DeployServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/maint/service/impl/DeployServiceImpl.java @@ -263,9 +263,13 @@ public class DeployServiceImpl implements DeployService { return "执行完毕"; } - private boolean checkFile(ExecuteShellUtil executeShellUtil, AppDto appDTO) { - String result = executeShellUtil.executeForResult("find " + appDTO.getDeployPath() + " -name " + appDTO.getName()); - return result.indexOf(appDTO.getName())>0; + private boolean checkFile(ExecuteShellUtil executeShellUtil, AppDto app) { + String deployPath = app.getDeployPath(); + String appName = app.getName(); + // 使用安全的命令执行方式,避免直接拼接字符串,https://github.com/elunez/eladmin/issues/873 + String[] command = {"find", deployPath, "-name", appName}; + String result = executeShellUtil.executeForResult(Arrays.toString(command)); + return result.contains(appName); } /** diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java index 73659ca0..e1eb0c0e 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java @@ -42,8 +42,8 @@ import java.util.concurrent.TimeUnit; @Component public class TokenProvider implements InitializingBean { + private Key signingKey; private JwtParser jwtParser; - private JwtBuilder jwtBuilder; private final RedisUtils redisUtils; private final SecurityProperties properties; public static final String AUTHORITIES_UUID_KEY = "uid"; @@ -56,13 +56,13 @@ public class TokenProvider implements InitializingBean { @Override public void afterPropertiesSet() { + // 解码Base64密钥并创建签名密钥 byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret()); - Key key = Keys.hmacShaKeyFor(keyBytes); + this.signingKey = Keys.hmacShaKeyFor(keyBytes); + // 初始化 JwtParser jwtParser = Jwts.parserBuilder() - .setSigningKey(key) + .setSigningKey(signingKey) // 使用预生成的签名密钥 .build(); - jwtBuilder = Jwts.builder() - .signWith(key, SignatureAlgorithm.HS512); } /** @@ -79,9 +79,14 @@ public class TokenProvider implements InitializingBean { claims.put(AUTHORITIES_UID_KEY, user.getUser().getId()); // 设置UUID,确保每次Token不一样 claims.put(AUTHORITIES_UUID_KEY, IdUtil.simpleUUID()); - return jwtBuilder + // 直接调用 Jwts.builder() 创建新实例 + return Jwts.builder() + // 设置自定义 Claims .setClaims(claims) + // 设置主题 .setSubject(user.getUsername()) + // 使用预生成的签名密钥和算法签名 + .signWith(signingKey, SignatureAlgorithm.HS512) .compact(); } diff --git a/eladmin-system/src/main/resources/config/application-dev.yml b/eladmin-system/src/main/resources/config/application-dev.yml index 1707a6ac..f9939136 100644 --- a/eladmin-system/src/main/resources/config/application-dev.yml +++ b/eladmin-system/src/main/resources/config/application-dev.yml @@ -116,3 +116,22 @@ file: # 文件大小 /M maxSize: 1 avatarMaxSize: 5 + +# 亚马逊S3协议云存储配置 +#支持七牛云,阿里云OSS,腾讯云COS,华为云OBS,移动云EOS等 +amz: + s3: + # 地域 + region: test + # 地域对应的 endpoint + endPoint: https://s3.test.com + # 访问的域名 + domain: https://s3.test.com + # 账号的认证信息,或者子账号的认证信息 + accessKey: 填写你的AccessKey + secretKey: 填写你的SecretKey + # 存储桶(Bucket) + defaultBucket: 填写你的存储桶名称 + # 文件存储路径 + timeformat: yyyy-MM + diff --git a/eladmin-system/src/main/resources/config/application-prod.yml b/eladmin-system/src/main/resources/config/application-prod.yml index 421d0358..8f7f615e 100644 --- a/eladmin-system/src/main/resources/config/application-prod.yml +++ b/eladmin-system/src/main/resources/config/application-prod.yml @@ -127,3 +127,21 @@ file: # 文件大小 /M maxSize: 1 avatarMaxSize: 5 + +# 亚马逊S3协议云存储配置 +#支持七牛云,阿里云OSS,腾讯云COS,华为云OBS,移动云EOS等 +amz: + s3: + # 地域 + region: test + # 地域对应的 endpoint + endPoint: https://s3.test.com + # 访问的域名 + domain: https://s3.test.com + # 账号的认证信息,或者子账号的认证信息 + accessKey: 填写你的AccessKey + secretKey: 填写你的SecretKey + # 存储桶(Bucket) + defaultBucket: 填写你的存储桶名称 + # 文件存储路径 + timeformat: yyyy-MM \ No newline at end of file diff --git a/eladmin-system/src/main/resources/config/application.yml b/eladmin-system/src/main/resources/config/application.yml index a428714e..f4dfc039 100644 --- a/eladmin-system/src/main/resources/config/application.yml +++ b/eladmin-system/src/main/resources/config/application.yml @@ -65,11 +65,6 @@ task: # 队列容量 queue-capacity: 30 -#七牛云 -qiniu: - # 文件大小 /M - max-size: 1 - #邮箱验证码有效时间/秒 code: expiration: 300 diff --git a/eladmin-tools/pom.xml b/eladmin-tools/pom.xml index 24cd54cd..cde0be3a 100644 --- a/eladmin-tools/pom.xml +++ b/eladmin-tools/pom.xml @@ -44,5 +44,13 @@ alipay-sdk-java ${alipay.version} + + + + software.amazon.awssdk + s3 + 2.30.13 + compile + diff --git a/eladmin-tools/src/main/java/me/zhengjie/config/AmzS3Config.java b/eladmin-tools/src/main/java/me/zhengjie/config/AmzS3Config.java new file mode 100644 index 00000000..fcf952a8 --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/config/AmzS3Config.java @@ -0,0 +1,78 @@ +package me.zhengjie.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import java.net.URI; + +/** + * @author Zheng Jie + * @date 2025-06-25 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "amz.s3") +public class AmzS3Config { + + /** + * Amazon S3 的区域配置,例如 "us-west-2"。 + * 该区域决定了 S3 存储桶的地理位置。 + */ + private String region; + + /** + * Amazon S3 的端点 URL + * 该端点用于访问 S3 服务。 + */ + private String endPoint; + + /** + * Amazon S3 的域名 + * 该域名用于构建访问 S3 服务的完整 URL。 + */ + private String domain; + + /** + * Amazon S3 的访问密钥 ID,用于身份验证。 + * 该密钥与 secretKey 一起使用来授权对 S3 服务的访问。 + */ + private String accessKey; + + /** + * Amazon S3 的秘密访问密钥,用于身份验证。 + * 该密钥与 accessKey 一起使用来授权对 S3 服务的访问。 + */ + private String secretKey; + + /** + * 默认的 S3 存储桶名称。 + * 该存储桶用于存储上传的文件和数据。 + */ + private String defaultBucket; + + /** + * 文件上传后存储的文件夹格式,默认为 "yyyy-MM"。 + */ + private String timeformat; + + /** + * 创建并返回一个 AmazonS3 客户端实例。 + * 使用当前配置类的 endPoint, region, accessKey 和 secretKey。 + * 声明为 @Bean 后,Spring 会将其作为单例管理,并在需要时自动注入。 + * + * @return 配置好的 AmazonS3 客户端实例 + */ + @Bean + public S3Client amazonS3Client() { + return S3Client.builder().region(Region.of(region)) + .endpointOverride(URI.create(endPoint)) + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey))) + .build(); + } +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/QiniuConfig.java b/eladmin-tools/src/main/java/me/zhengjie/domain/QiniuConfig.java deleted file mode 100644 index f9140b1d..00000000 --- a/eladmin-tools/src/main/java/me/zhengjie/domain/QiniuConfig.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2019-2025 Zheng Jie - * - * 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 me.zhengjie.domain; - -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import javax.persistence.*; -import javax.validation.constraints.NotBlank; -import java.io.Serializable; - -/** - * 七牛云对象存储配置类 - * @author Zheng Jie - * @date 2018-12-31 - */ -@Data -@Entity -@Table(name = "tool_qiniu_config") -public class QiniuConfig implements Serializable { - - @Id - @Column(name = "config_id") - @ApiModelProperty(value = "ID") - private Long id; - - @NotBlank - @ApiModelProperty(value = "accessKey") - private String accessKey; - - @NotBlank - @ApiModelProperty(value = "secretKey") - private String secretKey; - - @NotBlank - @ApiModelProperty(value = "存储空间名称作为唯一的 Bucket 识别符") - private String bucket; - - /** - * Zone表示与机房的对应关系 - * 华东 Zone.zone0() - * 华北 Zone.zone1() - * 华南 Zone.zone2() - * 北美 Zone.zoneNa0() - * 东南亚 Zone.zoneAs0() - */ - @NotBlank - @ApiModelProperty(value = "Zone表示与机房的对应关系") - private String zone; - - @NotBlank - @ApiModelProperty(value = "外链域名,可自定义,需在七牛云绑定") - private String host; - - @ApiModelProperty(value = "空间类型:公开/私有") - private String type = "公开"; -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/QiniuContent.java b/eladmin-tools/src/main/java/me/zhengjie/domain/QiniuContent.java deleted file mode 100644 index b7511db0..00000000 --- a/eladmin-tools/src/main/java/me/zhengjie/domain/QiniuContent.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2019-2025 Zheng Jie - * - * 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 me.zhengjie.domain; - -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import org.hibernate.annotations.UpdateTimestamp; -import javax.persistence.*; -import java.io.Serializable; -import java.sql.Timestamp; - -/** - * 上传成功后,存储结果 - * @author Zheng Jie - * @date 2018-12-31 - */ -@Data -@Entity -@Table(name = "tool_qiniu_content") -public class QiniuContent implements Serializable { - - @Id - @Column(name = "content_id") - @ApiModelProperty(value = "ID", hidden = true) - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "name") - @ApiModelProperty(value = "文件名") - private String key; - - @ApiModelProperty(value = "空间名") - private String bucket; - - @ApiModelProperty(value = "大小") - private String size; - - @ApiModelProperty(value = "文件地址") - private String url; - - @ApiModelProperty(value = "文件类型") - private String suffix; - - @ApiModelProperty(value = "空间类型:公开/私有") - private String type = "公开"; - - @UpdateTimestamp - @ApiModelProperty(value = "创建或更新时间") - @Column(name = "update_time") - private Timestamp updateTime; -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/domain/S3Storage.java b/eladmin-tools/src/main/java/me/zhengjie/domain/S3Storage.java new file mode 100644 index 00000000..389f6273 --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/domain/S3Storage.java @@ -0,0 +1,72 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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 me.zhengjie.domain; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.zhengjie.base.BaseEntity; +import javax.persistence.*; +import javax.validation.constraints.NotBlank; +import java.io.Serializable; + +/** +* @description S3存储实体类 +* @author Zheng Jie +* @date 2025-06-25 +**/ +@Data +@Entity +@Table(name = "tool_s3_storage") +@EqualsAndHashCode(callSuper = true) +public class S3Storage extends BaseEntity implements Serializable { + + @Id + @Column(name = "storage_id") + @ApiModelProperty(value = "ID", hidden = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotBlank + @ApiModelProperty(value = "文件名称") + private String fileName; + + @NotBlank + @ApiModelProperty(value = "真实存储的名称") + private String fileRealName; + + @NotBlank + @ApiModelProperty(value = "文件大小") + private String fileSize; + + @NotBlank + @ApiModelProperty(value = "文件MIME 类型") + private String fileMimeType; + + @NotBlank + @ApiModelProperty(value = "文件类型") + private String fileType; + + @NotBlank + @ApiModelProperty(value = "文件路径") + private String filePath; + + public void copy(S3Storage source){ + BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true)); + } +} diff --git a/eladmin-tools/src/main/java/me/zhengjie/repository/QiNiuConfigRepository.java b/eladmin-tools/src/main/java/me/zhengjie/repository/QiNiuConfigRepository.java deleted file mode 100644 index eb9045a8..00000000 --- a/eladmin-tools/src/main/java/me/zhengjie/repository/QiNiuConfigRepository.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019-2025 Zheng Jie - * - * 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 me.zhengjie.repository; - -import me.zhengjie.domain.QiniuConfig; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; - -/** - * @author Zheng Jie - * @date 2018-12-31 - */ -public interface QiNiuConfigRepository extends JpaRepository { - - /** - * 编辑类型 - * @param type / - */ - @Modifying - @Query(value = "update QiniuConfig set type = ?1") - void update(String type); -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/repository/QiniuContentRepository.java b/eladmin-tools/src/main/java/me/zhengjie/repository/S3StorageRepository.java similarity index 62% rename from eladmin-tools/src/main/java/me/zhengjie/repository/QiniuContentRepository.java rename to eladmin-tools/src/main/java/me/zhengjie/repository/S3StorageRepository.java index 10b0e157..9ad9f2a8 100644 --- a/eladmin-tools/src/main/java/me/zhengjie/repository/QiniuContentRepository.java +++ b/eladmin-tools/src/main/java/me/zhengjie/repository/S3StorageRepository.java @@ -15,20 +15,22 @@ */ package me.zhengjie.repository; -import me.zhengjie.domain.QiniuContent; +import me.zhengjie.domain.S3Storage; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; /** - * @author Zheng Jie - * @date 2018-12-31 - */ -public interface QiniuContentRepository extends JpaRepository, JpaSpecificationExecutor { +* @author Zheng Jie +* @date 2025-06-25 +*/ +public interface S3StorageRepository extends JpaRepository, JpaSpecificationExecutor { - /** - * 根据key查询 - * @param key 文件名 - * @return QiniuContent - */ - QiniuContent findByKey(String key); -} + /** + * 根据ID查询文件路径 + * @param id 文件ID + * @return 文件路径 + */ + @Query(value = "SELECT file_path FROM s3_storage WHERE id = ?1", nativeQuery = true) + String selectFilePathById(Long id); +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/rest/QiniuController.java b/eladmin-tools/src/main/java/me/zhengjie/rest/QiniuController.java deleted file mode 100644 index f17ce3bb..00000000 --- a/eladmin-tools/src/main/java/me/zhengjie/rest/QiniuController.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2019-2025 Zheng Jie - * - * 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 me.zhengjie.rest; - -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import me.zhengjie.annotation.Log; -import me.zhengjie.domain.QiniuConfig; -import me.zhengjie.domain.QiniuContent; -import me.zhengjie.service.dto.QiniuQueryCriteria; -import me.zhengjie.service.QiNiuService; -import me.zhengjie.utils.PageResult; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -/** - * 发送邮件 - * @author 郑杰 - * @date 2018/09/28 6:55:53 - */ -@Slf4j -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/qiNiuContent") -@Api(tags = "工具:七牛云存储管理") -public class QiniuController { - - private final QiNiuService qiNiuService; - - @GetMapping(value = "/config") - public ResponseEntity queryQiNiuConfig(){ - return new ResponseEntity<>(qiNiuService.find(), HttpStatus.OK); - } - - @Log("配置七牛云存储") - @ApiOperation("配置七牛云存储") - @PutMapping(value = "/config") - public ResponseEntity updateQiNiuConfig(@Validated @RequestBody QiniuConfig qiniuConfig){ - qiNiuService.config(qiniuConfig); - qiNiuService.update(qiniuConfig.getType()); - return new ResponseEntity<>(HttpStatus.OK); - } - - @ApiOperation("导出数据") - @GetMapping(value = "/download") - public void exportQiNiu(HttpServletResponse response, QiniuQueryCriteria criteria) throws IOException { - qiNiuService.downloadList(qiNiuService.queryAll(criteria), response); - } - - @ApiOperation("查询文件") - @GetMapping - public ResponseEntity> queryQiNiu(QiniuQueryCriteria criteria, Pageable pageable){ - return new ResponseEntity<>(qiNiuService.queryAll(criteria,pageable),HttpStatus.OK); - } - - @ApiOperation("上传文件") - @PostMapping - public ResponseEntity uploadQiNiu(@RequestParam MultipartFile file){ - QiniuContent qiniuContent = qiNiuService.upload(file,qiNiuService.find()); - Map map = new HashMap<>(3); - map.put("id",qiniuContent.getId()); - map.put("errno",0); - map.put("data",new String[]{qiniuContent.getUrl()}); - return new ResponseEntity<>(map,HttpStatus.OK); - } - - @Log("同步七牛云数据") - @ApiOperation("同步七牛云数据") - @PostMapping(value = "/synchronize") - public ResponseEntity synchronizeQiNiu(){ - qiNiuService.synchronize(qiNiuService.find()); - return new ResponseEntity<>(HttpStatus.OK); - } - - @Log("下载文件") - @ApiOperation("下载文件") - @GetMapping(value = "/download/{id}") - public ResponseEntity downloadQiNiu(@PathVariable Long id){ - Map map = new HashMap<>(1); - map.put("url", qiNiuService.download(qiNiuService.findByContentId(id),qiNiuService.find())); - return new ResponseEntity<>(map,HttpStatus.OK); - } - - @Log("删除文件") - @ApiOperation("删除文件") - @DeleteMapping(value = "/{id}") - public ResponseEntity deleteQiNiu(@PathVariable Long id){ - qiNiuService.delete(qiNiuService.findByContentId(id),qiNiuService.find()); - return new ResponseEntity<>(HttpStatus.OK); - } - - @Log("删除多张图片") - @ApiOperation("删除多张图片") - @DeleteMapping - public ResponseEntity deleteAllQiNiu(@RequestBody Long[] ids) { - qiNiuService.deleteAll(ids, qiNiuService.find()); - return new ResponseEntity<>(HttpStatus.OK); - } -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/rest/S3StorageController.java b/eladmin-tools/src/main/java/me/zhengjie/rest/S3StorageController.java new file mode 100644 index 00000000..964976e1 --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/rest/S3StorageController.java @@ -0,0 +1,104 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * 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 me.zhengjie.rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.annotation.Log; +import me.zhengjie.config.AmzS3Config; +import me.zhengjie.domain.S3Storage; +import me.zhengjie.service.S3StorageService; +import me.zhengjie.service.dto.S3StorageQueryCriteria; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * amz S3 协议云存储管理 + * @author 郑杰 + * @date 2025-06-25 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/s3Storage") +@Api(tags = "工具:S3协议云存储管理") +public class S3StorageController { + + private final AmzS3Config amzS3Config; + private final S3StorageService s3StorageService; + + @ApiOperation("导出数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('storage:list')") + public void exportS3Storage(HttpServletResponse response, S3StorageQueryCriteria criteria) throws IOException { + s3StorageService.download(s3StorageService.queryAll(criteria), response); + } + + @GetMapping + @ApiOperation("查询文件") + @PreAuthorize("@el.check('storage:list')") + public ResponseEntity> queryS3Storage(S3StorageQueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(s3StorageService.queryAll(criteria, pageable),HttpStatus.OK); + } + + @PostMapping + @ApiOperation("上传文件") + public ResponseEntity uploadS3Storage(@RequestParam MultipartFile file){ + S3Storage storage = s3StorageService.upload(file); + Map map = new HashMap<>(3); + map.put("id",storage.getId()); + map.put("errno",0); + map.put("data",new String[]{amzS3Config.getDomain() + "/" + storage.getFilePath()}); + return new ResponseEntity<>(map,HttpStatus.OK); + } + + @Log("下载文件") + @ApiOperation("下载文件") + @GetMapping(value = "/download/{id}") + public ResponseEntity downloadS3Storage(@PathVariable Long id){ + Map map = new HashMap<>(1); + S3Storage storage = s3StorageService.getById(id); + if (storage == null) { + map.put("message", "文件不存在或已被删除"); + return new ResponseEntity<>(map, HttpStatus.NOT_FOUND); + } + // 仅适合公开文件访问,私有文件可以使用服务中的 privateDownload 方法 + String url = amzS3Config.getDomain() + "/" + storage.getFilePath(); + map.put("url", url); + return new ResponseEntity<>(map,HttpStatus.OK); + } + + @Log("删除多个文件") + @DeleteMapping + @ApiOperation("删除多个文件") + @PreAuthorize("@el.check('storage:del')") + public ResponseEntity deleteAllS3Storage(@RequestBody List ids) { + s3StorageService.deleteAll(ids); + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/QiNiuService.java b/eladmin-tools/src/main/java/me/zhengjie/service/QiNiuService.java deleted file mode 100644 index b75a5aa1..00000000 --- a/eladmin-tools/src/main/java/me/zhengjie/service/QiNiuService.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2019-2025 Zheng Jie - * - * 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 me.zhengjie.service; - -import me.zhengjie.domain.QiniuConfig; -import me.zhengjie.domain.QiniuContent; -import me.zhengjie.service.dto.QiniuQueryCriteria; -import me.zhengjie.utils.PageResult; -import org.springframework.data.domain.Pageable; -import org.springframework.web.multipart.MultipartFile; - -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.List; - -/** - * @author Zheng Jie - * @date 2018-12-31 - */ -public interface QiNiuService { - - /** - * 查配置 - * @return QiniuConfig - */ - QiniuConfig find(); - - /** - * 修改配置 - * @param qiniuConfig 配置 - * @return QiniuConfig - */ - QiniuConfig config(QiniuConfig qiniuConfig); - - /** - * 分页查询 - * @param criteria 条件 - * @param pageable 分页参数 - * @return / - */ - PageResult queryAll(QiniuQueryCriteria criteria, Pageable pageable); - - /** - * 查询全部 - * @param criteria 条件 - * @return / - */ - List queryAll(QiniuQueryCriteria criteria); - - /** - * 上传文件 - * @param file 文件 - * @param qiniuConfig 配置 - * @return QiniuContent - */ - QiniuContent upload(MultipartFile file, QiniuConfig qiniuConfig); - - /** - * 查询文件 - * @param id 文件ID - * @return QiniuContent - */ - QiniuContent findByContentId(Long id); - - /** - * 下载文件 - * @param content 文件信息 - * @param config 配置 - * @return String - */ - String download(QiniuContent content, QiniuConfig config); - - /** - * 删除文件 - * @param content 文件 - * @param config 配置 - */ - void delete(QiniuContent content, QiniuConfig config); - - /** - * 同步数据 - * @param config 配置 - */ - void synchronize(QiniuConfig config); - - /** - * 删除文件 - * @param ids 文件ID数组 - * @param config 配置 - */ - void deleteAll(Long[] ids, QiniuConfig config); - - /** - * 更新数据 - * @param type 类型 - */ - void update(String type); - - /** - * 导出数据 - * @param queryAll / - * @param response / - * @throws IOException / - */ - void downloadList(List queryAll, HttpServletResponse response) throws IOException; -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/S3StorageService.java b/eladmin-tools/src/main/java/me/zhengjie/service/S3StorageService.java new file mode 100644 index 00000000..1cb3d3eb --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/service/S3StorageService.java @@ -0,0 +1,83 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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 me.zhengjie.service; + +import me.zhengjie.domain.S3Storage; +import me.zhengjie.service.dto.S3StorageQueryCriteria; +import me.zhengjie.utils.PageResult; +import org.springframework.data.domain.Pageable; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** +* @description 服务接口 +* @author Zheng Jie +* @date 2025-06-25 +**/ +public interface S3StorageService { + + /** + * 查询数据分页 + * @param criteria 条件 + * @param pageable 分页参数 + * @return PageResult + */ + PageResult queryAll(S3StorageQueryCriteria criteria, Pageable pageable); + + /** + * 查询所有数据不分页 + * @param criteria 条件参数 + * @return List + */ + List queryAll(S3StorageQueryCriteria criteria); + + /** + * 多选删除 + * @param ids / + */ + void deleteAll(List ids); + + /** + * 导出数据 + * @param all 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List all, HttpServletResponse response) throws IOException; + + /** + * 私有化下载,仅供参考,还有许多方式 + * @param id 文件ID + */ + Map privateDownload(Long id); + + /** + * 上传文件 + * @param file 上传的文件 + * @return S3Storage 对象,包含文件存储信息 + */ + S3Storage upload(MultipartFile file); + + /** + * 根据ID获取文件信息 + * @param id 文件ID + * @return S3Storage 对象,包含文件存储信息 + */ + S3Storage getById(Long id); +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/dto/QiniuQueryCriteria.java b/eladmin-tools/src/main/java/me/zhengjie/service/dto/QiniuQueryCriteria.java deleted file mode 100644 index fab27714..00000000 --- a/eladmin-tools/src/main/java/me/zhengjie/service/dto/QiniuQueryCriteria.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2019-2025 Zheng Jie - * - * 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 me.zhengjie.service.dto; - -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import me.zhengjie.annotation.Query; - -import java.sql.Timestamp; -import java.util.List; - -/** - * @author Zheng Jie - * @date 2019-6-4 09:54:37 - */ -@Data -public class QiniuQueryCriteria{ - - @ApiModelProperty(value = "名称查询") - @Query(type = Query.Type.INNER_LIKE) - private String key; - - @ApiModelProperty(value = "创建时间") - @Query(type = Query.Type.BETWEEN) - private List createTime; -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/dto/S3StorageQueryCriteria.java b/eladmin-tools/src/main/java/me/zhengjie/service/dto/S3StorageQueryCriteria.java new file mode 100644 index 00000000..539a1bdc --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/service/dto/S3StorageQueryCriteria.java @@ -0,0 +1,39 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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 me.zhengjie.service.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import me.zhengjie.annotation.Query; +import java.sql.Timestamp; +import java.util.List; + +/** +* @author Zheng Jie +* @date 2025-06-25 +**/ +@Data +public class S3StorageQueryCriteria { + + @Query(type = Query.Type.INNER_LIKE) + @ApiModelProperty(value = "文件名称") + private String fileName; + + @Query(type = Query.Type.BETWEEN) + @ApiModelProperty(value = "创建时间") + private List createTime; + +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/impl/QiNiuServiceImpl.java b/eladmin-tools/src/main/java/me/zhengjie/service/impl/QiNiuServiceImpl.java deleted file mode 100644 index 0e4d3c63..00000000 --- a/eladmin-tools/src/main/java/me/zhengjie/service/impl/QiNiuServiceImpl.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright 2019-2025 Zheng Jie - * - * 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 me.zhengjie.service.impl; - -import com.alibaba.fastjson2.JSON; -import com.qiniu.common.QiniuException; -import com.qiniu.http.Response; -import com.qiniu.storage.BucketManager; -import com.qiniu.storage.Configuration; -import com.qiniu.storage.UploadManager; -import com.qiniu.storage.model.DefaultPutRet; -import com.qiniu.storage.model.FileInfo; -import com.qiniu.util.Auth; -import lombok.RequiredArgsConstructor; -import me.zhengjie.domain.QiniuConfig; -import me.zhengjie.domain.QiniuContent; -import me.zhengjie.repository.QiniuContentRepository; -import me.zhengjie.service.dto.QiniuQueryCriteria; -import me.zhengjie.utils.*; -import me.zhengjie.exception.BadRequestException; -import me.zhengjie.repository.QiNiuConfigRepository; -import me.zhengjie.service.QiNiuService; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.*; - -/** - * @author Zheng Jie - * @date 2018-12-31 - */ -@Service -@RequiredArgsConstructor -@CacheConfig(cacheNames = "qiNiu") -public class QiNiuServiceImpl implements QiNiuService { - - private final QiNiuConfigRepository qiNiuConfigRepository; - private final QiniuContentRepository qiniuContentRepository; - - @Value("${qiniu.max-size}") - private Long maxSize; - - @Override - @Cacheable(key = "'config'") - public QiniuConfig find() { - Optional qiniuConfig = qiNiuConfigRepository.findById(1L); - return qiniuConfig.orElseGet(QiniuConfig::new); - } - - @Override - @CachePut(key = "'config'") - @Transactional(rollbackFor = Exception.class) - public QiniuConfig config(QiniuConfig qiniuConfig) { - qiniuConfig.setId(1L); - String http = "http://", https = "https://"; - if (!(qiniuConfig.getHost().toLowerCase().startsWith(http)||qiniuConfig.getHost().toLowerCase().startsWith(https))) { - throw new BadRequestException("外链域名必须以http://或者https://开头"); - } - return qiNiuConfigRepository.save(qiniuConfig); - } - - @Override - public PageResult queryAll(QiniuQueryCriteria criteria, Pageable pageable){ - return PageUtil.toPage(qiniuContentRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable)); - } - - @Override - public List queryAll(QiniuQueryCriteria criteria) { - return qiniuContentRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public QiniuContent upload(MultipartFile file, QiniuConfig qiniuConfig) { - FileUtil.checkSize(maxSize, file.getSize()); - if(qiniuConfig.getId() == null){ - throw new BadRequestException("请先添加相应配置,再操作"); - } - // 构造一个带指定Zone对象的配置类 - Configuration cfg = new Configuration(QiNiuUtil.getRegion(qiniuConfig.getZone())); - UploadManager uploadManager = new UploadManager(cfg); - Auth auth = Auth.create(qiniuConfig.getAccessKey(), qiniuConfig.getSecretKey()); - String upToken = auth.uploadToken(qiniuConfig.getBucket()); - try { - String key = file.getOriginalFilename(); - if(qiniuContentRepository.findByKey(key) != null) { - key = QiNiuUtil.getKey(key); - } - Response response = uploadManager.put(file.getBytes(), key, upToken); - //解析上传成功的结果 - - DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class); - QiniuContent content = qiniuContentRepository.findByKey(FileUtil.getFileNameNoEx(putRet.key)); - if(content == null){ - //存入数据库 - QiniuContent qiniuContent = new QiniuContent(); - qiniuContent.setSuffix(FileUtil.getExtensionName(putRet.key)); - qiniuContent.setBucket(qiniuConfig.getBucket()); - qiniuContent.setType(qiniuConfig.getType()); - qiniuContent.setKey(FileUtil.getFileNameNoEx(putRet.key)); - qiniuContent.setUrl(qiniuConfig.getHost()+"/"+putRet.key); - qiniuContent.setSize(FileUtil.getSize(Integer.parseInt(String.valueOf(file.getSize())))); - return qiniuContentRepository.save(qiniuContent); - } - return content; - } catch (Exception e) { - throw new BadRequestException(e.getMessage()); - } - } - - @Override - public QiniuContent findByContentId(Long id) { - QiniuContent qiniuContent = qiniuContentRepository.findById(id).orElseGet(QiniuContent::new); - ValidationUtil.isNull(qiniuContent.getId(),"QiniuContent", "id",id); - return qiniuContent; - } - - @Override - public String download(QiniuContent content,QiniuConfig config){ - String finalUrl; - String type = "公开"; - if(type.equals(content.getType())){ - finalUrl = content.getUrl(); - } else { - Auth auth = Auth.create(config.getAccessKey(), config.getSecretKey()); - // 1小时,可以自定义链接过期时间 - long expireInSeconds = 3600; - finalUrl = auth.privateDownloadUrl(content.getUrl(), expireInSeconds); - } - return finalUrl; - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void delete(QiniuContent content, QiniuConfig config) { - //构造一个带指定Zone对象的配置类 - Configuration cfg = new Configuration(QiNiuUtil.getRegion(config.getZone())); - Auth auth = Auth.create(config.getAccessKey(), config.getSecretKey()); - BucketManager bucketManager = new BucketManager(auth, cfg); - try { - bucketManager.delete(content.getBucket(), content.getKey() + "." + content.getSuffix()); - qiniuContentRepository.delete(content); - } catch (QiniuException ex) { - qiniuContentRepository.delete(content); - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void synchronize(QiniuConfig config) { - if(config.getId() == null){ - throw new BadRequestException("请先添加相应配置,再操作"); - } - //构造一个带指定Zone对象的配置类 - Configuration cfg = new Configuration(QiNiuUtil.getRegion(config.getZone())); - Auth auth = Auth.create(config.getAccessKey(), config.getSecretKey()); - BucketManager bucketManager = new BucketManager(auth, cfg); - //文件名前缀 - String prefix = ""; - //每次迭代的长度限制,最大1000,推荐值 1000 - int limit = 1000; - //指定目录分隔符,列出所有公共前缀(模拟列出目录效果)。缺省值为空字符串 - String delimiter = ""; - //列举空间文件列表 - BucketManager.FileListIterator fileListIterator = bucketManager.createFileListIterator(config.getBucket(), prefix, limit, delimiter); - while (fileListIterator.hasNext()) { - //处理获取的file list结果 - QiniuContent qiniuContent; - FileInfo[] items = fileListIterator.next(); - for (FileInfo item : items) { - if(qiniuContentRepository.findByKey(FileUtil.getFileNameNoEx(item.key)) == null){ - qiniuContent = new QiniuContent(); - qiniuContent.setSize(FileUtil.getSize(Integer.parseInt(String.valueOf(item.fsize)))); - qiniuContent.setSuffix(FileUtil.getExtensionName(item.key)); - qiniuContent.setKey(FileUtil.getFileNameNoEx(item.key)); - qiniuContent.setType(config.getType()); - qiniuContent.setBucket(config.getBucket()); - qiniuContent.setUrl(config.getHost()+"/"+item.key); - qiniuContentRepository.save(qiniuContent); - } - } - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void deleteAll(Long[] ids, QiniuConfig config) { - for (Long id : ids) { - delete(findByContentId(id), config); - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void update(String type) { - qiNiuConfigRepository.update(type); - } - - @Override - public void downloadList(List queryAll, HttpServletResponse response) throws IOException { - List> list = new ArrayList<>(); - for (QiniuContent content : queryAll) { - Map map = new LinkedHashMap<>(); - map.put("文件名", content.getKey()); - map.put("文件类型", content.getSuffix()); - map.put("空间名称", content.getBucket()); - map.put("文件大小", content.getSize()); - map.put("空间类型", content.getType()); - map.put("创建日期", content.getUpdateTime()); - list.add(map); - } - FileUtil.downloadExcel(list, response); - } -} diff --git a/eladmin-tools/src/main/java/me/zhengjie/service/impl/S3StorageServiceImpl.java b/eladmin-tools/src/main/java/me/zhengjie/service/impl/S3StorageServiceImpl.java new file mode 100644 index 00000000..56d3ab3b --- /dev/null +++ b/eladmin-tools/src/main/java/me/zhengjie/service/impl/S3StorageServiceImpl.java @@ -0,0 +1,264 @@ +/* +* Copyright 2019-2025 Zheng Jie +* +* 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 me.zhengjie.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.IdUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.zhengjie.config.AmzS3Config; +import me.zhengjie.domain.S3Storage; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.repository.S3StorageRepository; +import me.zhengjie.service.S3StorageService; +import me.zhengjie.service.dto.S3StorageQueryCriteria; +import me.zhengjie.utils.*; +import org.apache.commons.io.IOUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.core.waiters.WaiterResponse; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.*; +import software.amazon.awssdk.services.s3.waiters.S3Waiter; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + +/** +* @description 服务实现 +* @author Zheng Jie +* @date 2025-06-25 +**/ +@Slf4j +@Service +@RequiredArgsConstructor +public class S3StorageServiceImpl implements S3StorageService { + + private final S3Client s3Client; + private final AmzS3Config amzS3Config; + private final S3StorageRepository s3StorageRepository; + + @Override + public S3Storage getById(Long id) { + return s3StorageRepository.findById(id).orElse(null); + } + + @Override + public PageResult queryAll(S3StorageQueryCriteria criteria, Pageable pageable){ + Page page = s3StorageRepository.findAll((root, criteriaQuery, criteriaBuilder) + -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + return PageUtil.toPage(page); + } + + @Override + public List queryAll(S3StorageQueryCriteria criteria){ + return s3StorageRepository.findAll((root, criteriaQuery, criteriaBuilder) + -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteAll(List ids) { + // 检查桶是否存在 + String bucketName = amzS3Config.getDefaultBucket(); + if (!bucketExists(bucketName)) { + throw new BadRequestException("存储桶不存在,请检查配置或权限。"); + } + // 遍历 ID 列表,删除对应的文件和数据库记录 + for (Long id : ids) { + String filePath = s3StorageRepository.selectFilePathById(id); + if (filePath == null) { + System.err.println("未找到 ID 为 " + id + " 的文件记录,无法删除。"); + continue; + } + try { + // 创建 DeleteObjectRequest,指定存储桶和文件键 + DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder() + .bucket(bucketName) + .key(filePath) + .build(); + // 调用 deleteObject 方法 + s3Client.deleteObject(deleteObjectRequest); + // 删除数据库数据 + s3StorageRepository.deleteById(id); + } catch (S3Exception e) { + // 处理 AWS 特定的异常 + log.error("从 S3 删除文件时出错: {}", e.awsErrorDetails().errorMessage(), e); + } + } + } + + @Override + public S3Storage upload(MultipartFile file) { + String bucketName = amzS3Config.getDefaultBucket(); + // 检查存储桶是否存在 + if (!bucketExists(bucketName)) { + log.warn("存储桶 {} 不存在,尝试创建...", bucketName); + if (createBucket(bucketName)){ + log.info("存储桶 {} 创建成功。", bucketName); + } else { + throw new BadRequestException("存储桶创建失败,请检查配置或权限。"); + } + } + // 获取文件名 + String originalName = file.getOriginalFilename(); + if (StringUtils.isBlank(originalName)) { + throw new IllegalArgumentException("文件名不能为空"); + } + // 生成存储路径和文件名 + String folder = DateUtil.format(new Date(), amzS3Config.getTimeformat()); + String fileName = IdUtil.simpleUUID() + "." + FileUtil.getExtensionName(originalName); + String filePath = folder + "/" + fileName; + // 构建上传请求 + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(amzS3Config.getDefaultBucket()) + .key(filePath) + .build(); + // 创建 S3Storage 实例 + S3Storage s3Storage = new S3Storage(); + try { + // 上传文件到 S3 + s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(file.getInputStream(), file.getSize())); + // 设置 S3Storage 属性 + s3Storage.setFileMimeType(FileUtil.getMimeType(originalName)); + s3Storage.setFileName(originalName); + s3Storage.setFileRealName(fileName); + s3Storage.setFileSize(FileUtil.getSize(file.getSize())); + s3Storage.setFileType(FileUtil.getExtensionName(originalName)); + s3Storage.setFilePath(filePath); + // 保存入库 + s3StorageRepository.save(s3Storage); + } catch (IOException e) { + throw new RuntimeException(e); + } + // 设置地址 + return s3Storage; + } + + @Override + public void download(List all, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (S3Storage s3Storage : all) { + Map map = new LinkedHashMap<>(); + map.put("文件名称", s3Storage.getFileName()); + map.put("真实存储的名称", s3Storage.getFileRealName()); + map.put("文件大小", s3Storage.getFileSize()); + map.put("文件MIME 类型", s3Storage.getFileMimeType()); + map.put("文件类型", s3Storage.getFileType()); + map.put("文件路径", s3Storage.getFilePath()); + map.put("创建者", s3Storage.getCreateBy()); + map.put("更新者", s3Storage.getUpdateBy()); + map.put("创建日期", s3Storage.getCreateTime()); + map.put("更新时间", s3Storage.getUpdateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } + + public Map privateDownload(Long id) { + S3Storage storage = s3StorageRepository.findById(id).orElse(null); + if (storage == null) { + throw new BadRequestException("文件不存在或已被删除"); + } + // 创建 GetObjectRequest,指定存储桶和文件键 + GetObjectRequest getObjectRequest = GetObjectRequest.builder() + .bucket(amzS3Config.getDefaultBucket()) + .key(storage.getFilePath()) + .build(); + String base64Data; + // 使用 try-with-resources 确保流能被自动关闭 + // s3Client.getObject() 返回一个 ResponseInputStream,它是一个包含S3对象数据的输入流 + try (ResponseInputStream s3InputStream = s3Client.getObject(getObjectRequest)) { + // 使用 IOUtils.toByteArray 将输入流直接转换为字节数组 + byte[] fileBytes = IOUtils.toByteArray(s3InputStream); + // 使用 Java 内置的 Base64 编码器将字节数组转换为 Base64 字符串 + base64Data = Base64.getEncoder().encodeToString(fileBytes); + } catch (S3Exception e) { + // 处理 AWS 特定的异常 + throw new BadRequestException("从 S3 下载文件时出错: " + e.awsErrorDetails().errorMessage()); + } catch (IOException e) { + // 处理通用的 IO 异常 (IOUtils.toByteArray 可能会抛出) + throw new BadRequestException("读取 S3 输入流时出错: " + e.getMessage()); + } + // 构造返回数据 + Map responseData = new HashMap<>(); + // 文件名 + responseData.put("fileName", storage.getFileName()); + // 文件类型 + responseData.put("fileMimeType", storage.getFileMimeType()); + // 文件内容 + responseData.put("base64Data", base64Data); + return responseData; + } + + /** + * 检查云存储桶是否存在 + * @param bucketName 存储桶名称 + */ + @SuppressWarnings({"all"}) + private boolean bucketExists(String bucketName) { + try { + HeadBucketRequest headBucketRequest = HeadBucketRequest.builder() + .bucket(bucketName) + .build(); + s3Client.headBucket(headBucketRequest); + return true; + } catch (S3Exception e) { + // 如果状态码是 404 (Not Found), 说明存储桶不存在 + if (e.statusCode() == 404) { + log.error("存储桶 '{}' 不存在。", bucketName); + return false; + } + // 其他异常 (如 403 Forbidden) 说明存在问题,但不能断定它不存在 + throw new BadRequestException("检查存储桶时出错: " + e.awsErrorDetails().errorMessage()); + } + } + + /** + * 创建云存储桶 + * @param bucketName 存储桶名称 + */ + private boolean createBucket(String bucketName) { + try { + // 使用 S3Waiter 等待存储桶创建完成 + S3Waiter s3Waiter = s3Client.waiter(); + CreateBucketRequest bucketRequest = CreateBucketRequest.builder() + .bucket(bucketName) + .acl(BucketCannedACL.PRIVATE) + .build(); + s3Client.createBucket(bucketRequest); + // 等待直到存储桶创建完成 + HeadBucketRequest bucketRequestWait = HeadBucketRequest.builder() + .bucket(bucketName) + .build(); + // 使用 WaiterResponse 等待存储桶存在 + WaiterResponse waiterResponse = s3Waiter.waitUntilBucketExists(bucketRequestWait); + waiterResponse.matched().response().ifPresent(response -> + log.info("存储桶 '{}' 创建成功,状态: {}", bucketName, response.sdkHttpResponse().statusCode()) + ); + } catch (BucketAlreadyOwnedByYouException e) { + log.warn("存储桶 '{}' 已经被您拥有,无需重复创建。", bucketName); + } catch (S3Exception e) { + throw new BadRequestException("创建存储桶时出错: " + e.awsErrorDetails().errorMessage()); + } + return true; + } +} \ No newline at end of file diff --git a/eladmin-tools/src/main/java/me/zhengjie/utils/QiNiuUtil.java b/eladmin-tools/src/main/java/me/zhengjie/utils/QiNiuUtil.java deleted file mode 100644 index 9d9ed895..00000000 --- a/eladmin-tools/src/main/java/me/zhengjie/utils/QiNiuUtil.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2019-2025 Zheng Jie - * - * 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 me.zhengjie.utils; - -import com.qiniu.storage.Region; -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * 七牛云存储工具类 - * @author Zheng Jie - * @date 2018-12-31 - */ -public class QiNiuUtil { - - private static final String HUAD = "华东"; - - private static final String HUAB = "华北"; - - private static final String HUAN = "华南"; - - private static final String BEIM = "北美"; - - /** - * 得到机房的对应关系 - * @param zone 机房名称 - * @return Region - */ - public static Region getRegion(String zone){ - - if(HUAD.equals(zone)){ - return Region.huadong(); - } else if(HUAB.equals(zone)){ - return Region.huabei(); - } else if(HUAN.equals(zone)){ - return Region.huanan(); - } else if (BEIM.equals(zone)){ - return Region.beimei(); - // 否则就是东南亚 - } else { - return Region.qvmHuadong(); - } - } - - /** - * 默认不指定key的情况下,以文件内容的hash值作为文件名 - * @param file 文件名 - * @return String - */ - public static String getKey(String file){ - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); - Date date = new Date(); - return FileUtil.getFileNameNoEx(file) + "-" + - sdf.format(date) + - "." + - FileUtil.getExtensionName(file); - } -} diff --git a/sql/eladmin.sql b/sql/eladmin.sql index 166afc37..aea1ee75 100644 --- a/sql/eladmin.sql +++ b/sql/eladmin.sql @@ -2,16 +2,16 @@ Navicat Premium Dump SQL Source Server : localhost - Source Server Type : MySQL + Source Server Type : MariaDB Source Server Version : 110206 (11.2.6-MariaDB) Source Host : localhost:3306 Source Schema : eladmin - Target Server Type : MySQL + Target Server Type : MariaDB Target Server Version : 110206 (11.2.6-MariaDB) File Encoding : 65001 - Date: 15/01/2025 18:20:01 + Date: 25/06/2025 15:56:51 */ SET NAMES utf8mb4; @@ -38,7 +38,7 @@ CREATE TABLE `code_column` ( `date_annotation` varchar(255) DEFAULT NULL COMMENT '日期注解', PRIMARY KEY (`column_id`) USING BTREE, KEY `idx_table_name` (`table_name`) -) ENGINE=InnoDB AUTO_INCREMENT=259 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='代码生成字段信息存储'; +) ENGINE=InnoDB AUTO_INCREMENT=266 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='代码生成字段信息存储'; -- ---------------------------- -- Records of code_column @@ -536,7 +536,7 @@ CREATE TABLE `sys_role` ( -- Records of sys_role -- ---------------------------- BEGIN; -INSERT INTO `sys_role` (`role_id`, `name`, `level`, `description`, `data_scope`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (1, '超级管理员', 1, '-', '全部', NULL, 'admin', '2018-11-23 11:04:37', '2020-08-06 16:10:24'); +INSERT INTO `sys_role` (`role_id`, `name`, `level`, `description`, `data_scope`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (1, '管理员', 1, '-', '全部', NULL, 'admin', '2018-11-23 11:04:37', '2025-01-21 14:53:13'); INSERT INTO `sys_role` (`role_id`, `name`, `level`, `description`, `data_scope`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (2, '普通用户', 2, '-', '本级', NULL, 'admin', '2018-11-23 13:09:06', '2020-09-05 10:45:12'); COMMIT; @@ -705,8 +705,8 @@ CREATE TABLE `sys_user` ( -- Records of sys_user -- ---------------------------- BEGIN; -INSERT INTO `sys_user` (`user_id`, `dept_id`, `username`, `nick_name`, `gender`, `phone`, `email`, `avatar_name`, `avatar_path`, `password`, `is_admin`, `enabled`, `create_by`, `update_by`, `pwd_reset_time`, `create_time`, `update_time`) VALUES (1, 2, 'admin', '管理员', '男', '18888888888', '201507802@qq.com', 'avatar-20250114101539224.png', '/Users/jie/Documents/work/me/admin/eladmin-mp/eladmin/~/avatar/avatar-20250114101539224.png', '$2a$10$Egp1/gvFlt7zhlXVfEFw4OfWQCGPw0ClmMcc6FjTnvXNRVf9zdMRa', b'1', b'1', NULL, 'admin', '2020-05-03 16:38:31', '2018-08-23 09:11:56', '2020-09-05 10:43:31'); -INSERT INTO `sys_user` (`user_id`, `dept_id`, `username`, `nick_name`, `gender`, `phone`, `email`, `avatar_name`, `avatar_path`, `password`, `is_admin`, `enabled`, `create_by`, `update_by`, `pwd_reset_time`, `create_time`, `update_time`) VALUES (2, 2, 'test', '测试', '男', '19999999999', '231@qq.com', NULL, NULL, '$2a$10$4XcyudOYTSz6fue6KFNMHeUQnCX5jbBQypLEnGk1PmekXt5c95JcK', b'0', b'1', 'admin', 'admin', NULL, '2020-05-05 11:15:49', '2020-09-05 10:43:38'); +INSERT INTO `sys_user` (`user_id`, `dept_id`, `username`, `nick_name`, `gender`, `phone`, `email`, `avatar_name`, `avatar_path`, `password`, `is_admin`, `enabled`, `create_by`, `update_by`, `pwd_reset_time`, `create_time`, `update_time`) VALUES (1, 2, 'admin', '管理员', '男', '18888888888', '201507802@qq.com', 'avatar-20250122102642222.png', '/Users/jie/Documents/work/private/eladmin/~/avatar/avatar-20250122102642222.png', '$2a$10$Egp1/gvFlt7zhlXVfEFw4OfWQCGPw0ClmMcc6FjTnvXNRVf9zdMRa', b'1', b'1', NULL, 'admin', '2020-05-03 16:38:31', '2018-08-23 09:11:56', '2025-01-22 10:26:42'); +INSERT INTO `sys_user` (`user_id`, `dept_id`, `username`, `nick_name`, `gender`, `phone`, `email`, `avatar_name`, `avatar_path`, `password`, `is_admin`, `enabled`, `create_by`, `update_by`, `pwd_reset_time`, `create_time`, `update_time`) VALUES (2, 7, 'test', '测试', '男', '19999999999', '231@qq.com', NULL, NULL, '$2a$10$4XcyudOYTSz6fue6KFNMHeUQnCX5jbBQypLEnGk1PmekXt5c95JcK', b'0', b'1', 'admin', 'admin', NULL, '2020-05-05 11:15:49', '2025-01-21 14:53:04'); COMMIT; -- ---------------------------- @@ -820,47 +820,29 @@ BEGIN; COMMIT; -- ---------------------------- --- Table structure for tool_qiniu_config +-- Table structure for tool_s3_storage -- ---------------------------- -DROP TABLE IF EXISTS `tool_qiniu_config`; -CREATE TABLE `tool_qiniu_config` ( - `config_id` bigint(20) NOT NULL COMMENT 'ID', - `access_key` text DEFAULT NULL COMMENT 'accessKey', - `bucket` varchar(255) DEFAULT NULL COMMENT 'Bucket 识别符', - `host` varchar(255) NOT NULL COMMENT '外链域名', - `secret_key` text DEFAULT NULL COMMENT 'secretKey', - `type` varchar(255) DEFAULT NULL COMMENT '空间类型', - `zone` varchar(255) DEFAULT NULL COMMENT '机房', - PRIMARY KEY (`config_id`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='七牛云配置'; +DROP TABLE IF EXISTS `tool_s3_storage`; +CREATE TABLE `tool_s3_storage` ( + `storage_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `file_name` varchar(255) NOT NULL COMMENT '文件名称', + `file_real_name` varchar(255) NOT NULL COMMENT '真实存储的名称', + `file_size` varchar(100) NOT NULL COMMENT '文件大小', + `file_mime_type` varchar(50) NOT NULL COMMENT '文件MIME 类型', + `file_type` varchar(50) NOT NULL COMMENT '文件类型', + `file_path` tinytext NOT NULL COMMENT '文件路径', + `create_by` varchar(255) NOT NULL COMMENT '创建者', + `update_by` varchar(255) NOT NULL COMMENT '更新者', + `create_time` datetime NOT NULL COMMENT '创建日期', + `update_time` datetime NOT NULL COMMENT '更新时间', + PRIMARY KEY (`storage_id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='s3 协议对象存储'; -- ---------------------------- --- Records of tool_qiniu_config --- ---------------------------- -BEGIN; -COMMIT; - --- ---------------------------- --- Table structure for tool_qiniu_content --- ---------------------------- -DROP TABLE IF EXISTS `tool_qiniu_content`; -CREATE TABLE `tool_qiniu_content` ( - `content_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', - `bucket` varchar(255) DEFAULT NULL COMMENT 'Bucket 识别符', - `name` varchar(180) DEFAULT NULL COMMENT '文件名称', - `size` varchar(255) DEFAULT NULL COMMENT '文件大小', - `type` varchar(255) DEFAULT NULL COMMENT '文件类型:私有或公开', - `url` varchar(255) DEFAULT NULL COMMENT '文件url', - `suffix` varchar(255) DEFAULT NULL COMMENT '文件后缀', - `update_time` datetime DEFAULT NULL COMMENT '上传或同步的时间', - PRIMARY KEY (`content_id`) USING BTREE, - UNIQUE KEY `uniq_name` (`name`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='七牛云文件存储'; - --- ---------------------------- --- Records of tool_qiniu_content +-- Records of tool_s3_storage -- ---------------------------- BEGIN; +INSERT INTO `tool_s3_storage` (`storage_id`, `file_name`, `file_real_name`, `file_size`, `file_mime_type`, `file_type`, `file_path`, `create_by`, `update_by`, `create_time`, `update_time`) VALUES (4, 'tx.jpg', '2ca1de24d8fa422eae4ede30e97c46d8.jpg', '29.67KB', 'image/jpeg', 'jpg', '2025-06/2ca1de24d8fa422eae4ede30e97c46d8.jpg', 'admin', 'admin', '2025-06-25 15:48:22', '2025-06-25 15:48:22'); COMMIT; SET FOREIGN_KEY_CHECKS = 1;