Browse Source

文件上传下载

Gaokun Wang 2 tuần trước cách đây
mục cha
commit
6a7570c298

+ 2 - 2
config/local/security.yml

@@ -7,7 +7,6 @@ security:
     - /**/*.html
     - /**/*.css
     - /**/*.js
-    - /profile/**
     # 公共路径
     - /favicon.ico
     - /error
@@ -15,4 +14,5 @@ security:
     - /actuator
     - /actuator/**
     # 其它链接
-    - /auth/login
+    - /auth/login
+    - /system/files/download/*

+ 11 - 0
eco-common/com-core/src/main/java/org/eco/vip/core/constant/Constants.java

@@ -13,6 +13,12 @@ package org.eco.vip.core.constant;
  */
 public class Constants {
 
+    /**
+     * http请求
+     */
+    public static final String HTTP = "http://";
+
+
     /**
      * 所有顶层ID
      */
@@ -53,4 +59,9 @@ public class Constants {
      * 本地文件路径 unix
      */
     public static final String LOCAL_FILE_PATH_KEY = "localFilePath";
+
+    /**
+     * 本地文件默认桶名称
+     */
+    public static final String LOCAL_FILE_BUCKET_KEY = "LocalBucket";
 }

+ 33 - 0
eco-common/com-core/src/main/java/org/eco/vip/core/utils/DateUtils.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.core.utils;
+
+
+import cn.hutool.core.date.DateUtil;
+
+import java.util.Date;
+
+/**
+ * @description DateUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/23 14:23
+ */
+public class DateUtils extends DateUtil {
+    public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
+
+    /**
+     * 日期路径 年/月/日 如2018/08/08
+     */
+    public static String datePath() {
+        Date now = new Date();
+        return DateUtil.format(now, "yyyy/MM/dd");
+    }
+    public static String dateTimeNow() {
+        return DateUtil.format(new Date(), YYYYMMDDHHMMSS);
+    }
+
+}

+ 34 - 29
eco-common/com-core/src/main/java/org/eco/vip/core/utils/FileLocalUtils.java

@@ -64,19 +64,18 @@ public class FileLocalUtils {
      * 存储文件,不返回地址
      *
      * @param bucketName  桶名称
-     * @param key         文件唯一名
+     * @param path         文件唯一名
      * @param inputStream 文件流
      */
-    public static void storageFile(String bucketName, String key, InputStream inputStream) {
+    public static void storageFile(String bucketName, String path, InputStream inputStream) {
         initClient();
-        FileUtils.writeFromStream(inputStream, getUploadFilePath() + FileUtils.FILE_SEPARATOR + bucketName + FileUtils.FILE_SEPARATOR + key);
+        FileUtils.writeFromStream(inputStream, getUploadFilePath() + genUploadFileDir(bucketName, path));
     }
 
     /**
      * 获取上传地址
      *
-     * @author xuyuxiang
-     * @date 2022/1/5 23:24
+     * @return 上传地址
      */
     public static String getUploadFilePath() {
         return client.getStr(Constants.LOCAL_FILE_PATH_KEY);
@@ -86,58 +85,64 @@ public class FileLocalUtils {
      * 存储文件,不返回地址
      *
      * @param bucketName 桶名称
-     * @param key        文件唯一名
+     * @param path        文件唯一名
      * @param multipartFile      文件
      */
-    public static void storageFile(String bucketName, String key, MultipartFile multipartFile) {
+    public static void storageFile(String bucketName, String path, MultipartFile multipartFile) {
         InputStream inputStream;
         try {
             inputStream = multipartFile.getInputStream();
         } catch (IOException e) {
             throw new BusinessException("获取文件流异常:{}", multipartFile.getName());
         }
-        storageFile(bucketName, key, inputStream);
+        storageFile(bucketName, path, inputStream);
     }
 
     /**
      * 存储文件,返回存储的地址
      *
      * @param bucketName 桶名称
-     * @param key         文件唯一名
+     * @param path         文件唯一名
      * @param multipartFile      文件
      */
-    public static String storageFileWithReturnUrl(String bucketName, String key, MultipartFile multipartFile) {
-        storageFile(bucketName, key, multipartFile);
-        return getFileAuthUrl(bucketName, key);
+    public static String storageFileWithReturnUrl(String bucketName, String path, MultipartFile multipartFile) {
+        storageFile(bucketName, path, multipartFile);
+        return genUploadFileDir(bucketName, path);
     }
 
     /**
-     * 获取文件的实际存储地址
+     * 根据桶名称和文件path获取文件
      *
-     * @param bucketName 文件桶
-     * @param key         文件唯一名
+     * @param path        文件路径
      */
-    public static String getFileAuthUrl(String bucketName, String key) {
+    public static File getFileByBucketNameAndPath(String path) {
         initClient();
-        File file = getFileByBucketNameAndKey(bucketName, key);
-        return file.getAbsolutePath();
+        String newPath = getUploadFilePath() + path;
+        File file = FileUtils.file(newPath);
+        if (!FileUtils.exist(file)) {
+            throw new BusinessException("文件{}不存在", newPath);
+        }
+        return file;
     }
 
     /**
      * 根据桶名称和文件key获取文件
      *
      * @param bucketName 文件桶
-     * @param key        唯一标示id,例如a.txt, doc/a.txt
-     * @author xuyuxiang
-     * @date 2022/1/5 23:24
+     * @param key        文件唯一名
+     * @return 上传文件目录
      */
-    public static File getFileByBucketNameAndKey(String bucketName, String key) {
-        initClient();
-        String path = getUploadFilePath() + FileUtils.FILE_SEPARATOR + bucketName + FileUtils.FILE_SEPARATOR + key;
-        File file = FileUtils.file(path);
-        if (!FileUtils.exist(file)) {
-            throw new BusinessException("文件{}不存在", path);
-        }
-        return file;
+    public static String genUploadFileDir(String bucketName, String key) {
+        return FileUtils.FILE_SEPARATOR + bucketName + FileUtils.FILE_SEPARATOR + key;
+    }
+
+    /**
+     * 删除文件
+     *
+     * @param path        文件path
+     */
+    public static void deleteFile(String path) {
+        File file = getFileByBucketNameAndPath(path);
+        FileUtils.del(file);
     }
 }

+ 4 - 0
eco-common/com-core/src/main/java/org/eco/vip/core/utils/FileUtils.java

@@ -53,4 +53,8 @@ public class FileUtils extends FileUtil {
         String encode = URLEncoder.encode(s, StandardCharsets.UTF_8);
         return encode.replaceAll("\\+", "%20");
     }
+
+    public static String genFilePath(String key, String originalFileName) {
+        return StrUtils.format("{}{}{}_{}", DateUtils.datePath(),FileUtils.FILE_SEPARATOR, key, originalFileName);
+    }
 }

+ 50 - 0
eco-common/com-core/src/main/java/org/eco/vip/core/utils/IpUtils.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.core.utils;
+
+
+import cn.hutool.core.net.Ipv4Util;
+import lombok.extern.slf4j.Slf4j;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.Enumeration;
+
+/**
+ * @description IpUtils
+ *
+ * @author GaoKunW
+ * @date 2025/7/23 15:24
+ */
+@Slf4j
+public class IpUtils extends Ipv4Util {
+
+    public static String getIpAddress() {
+        try {
+            //从网卡中获取IP
+            Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
+            InetAddress ip;
+            while (allNetInterfaces.hasMoreElements()) {
+                NetworkInterface netInterface = allNetInterfaces.nextElement();
+                //用于排除回送接口,非虚拟网卡,未在使用中的网络接口
+                if (!netInterface.isLoopback() && !netInterface.isVirtual() && netInterface.isUp()) {
+                    //返回和网络接口绑定的所有IP地址
+                    Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
+                    while (addresses.hasMoreElements()) {
+                        ip = addresses.nextElement();
+                        if (ip instanceof Inet4Address) {
+                            return ip.getHostAddress();
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.info("IP地址获取失败{}", e.getMessage());
+        }
+        return "127.0.0.1";
+    }
+}

+ 87 - 0
eco-common/com-core/src/main/java/org/eco/vip/core/utils/Seqs.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.core.utils;
+
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @description Seqs
+ *
+ * @author GaoKunW
+ * @date 2025/7/23 14:46
+ */
+public class Seqs {
+    // 通用序列类型
+    public static final String COMM_SEQ_TYPE = "COMMON";
+
+    // 上传序列类型
+    public static final String UPLOAD_SEQ_TYPE = "UPLOAD";
+
+    // 通用接口序列数
+    private static final AtomicInteger COMM_SEQ = new AtomicInteger(1);
+
+    // 上传接口序列数
+    private static final AtomicInteger UPLOAD_SEQ = new AtomicInteger(1);
+
+    // 机器标识
+    private static final String MACHINE_CODE = "E";
+
+    /**
+     * 获取通用序列号
+     *
+     * @return 序列值
+     */
+    public static String getId() {
+        return getId(COMM_SEQ_TYPE);
+    }
+
+    /**
+     * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串
+     *
+     * @return 序列值
+     */
+    public static String getId(String type) {
+        AtomicInteger atomicInt = COMM_SEQ;
+        if (UPLOAD_SEQ_TYPE.equals(type)) {
+            atomicInt = UPLOAD_SEQ;
+        }
+        return getId(atomicInt, 3);
+    }
+
+    /**
+     * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串
+     *
+     * @param atomicInt 序列数
+     * @param length 数值长度
+     * @return 序列值
+     */
+    public static String getId(AtomicInteger atomicInt, int length) {
+
+        String result = DateUtils.dateTimeNow();
+        result += MACHINE_CODE;
+        result += getSeq(atomicInt, length);
+        return result;
+    }
+
+    /**
+     * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数
+     *
+     * @return 序列值
+     */
+    private synchronized static String getSeq(AtomicInteger atomicInt, int length) {
+        // 先取值再+1
+        int value = atomicInt.getAndIncrement();
+
+        // 如果更新后值>=10 的 (length)幂次方则重置为1
+        int maxSeq = (int) Math.pow(10, length);
+        if (atomicInt.get() >= maxSeq) {
+            atomicInt.set(1);
+        }
+        // 转字符串,用0左补齐
+        return StrUtils.padPre(StrUtils.toString(value), length, '0');
+    }
+}

+ 46 - 6
eco-nexus-core/nexus-core-biz/src/main/java/org/eco/vip/nexus/core/controller/files/FilesController.java

@@ -6,24 +6,34 @@
 package org.eco.vip.nexus.core.controller.files;
 
 
+import cn.dev33.satoken.annotation.SaCheckPermission;
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
 import org.eco.vip.core.constant.ConfigConstants;
 import org.eco.vip.core.pojo.CommonResult;
+import org.eco.vip.core.pojo.PageResult;
 import org.eco.vip.nexus.core.domain.files.pojo.FilesBO;
+import org.eco.vip.nexus.core.domain.files.pojo.FilesVO;
 import org.eco.vip.nexus.core.service.config.IConfigService;
 import org.eco.vip.nexus.core.service.files.IFilesService;
 import org.springframework.http.MediaType;
 import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestPart;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.IOException;
+import java.util.List;
 
+import static org.eco.vip.core.pojo.CommonResult.fail;
 import static org.eco.vip.core.pojo.CommonResult.success;
 
 /**
@@ -33,7 +43,7 @@ import static org.eco.vip.core.pojo.CommonResult.success;
  * @date 2025/7/22 11:07
  */
 @RestController
-@RequestMapping("/system/file")
+@RequestMapping("/system/files")
 @Validated
 public class FilesController {
 
@@ -43,14 +53,44 @@ public class FilesController {
     @Resource
     private IFilesService fileService;
 
+    @GetMapping("/page")
+    public CommonResult<PageResult<FilesVO>> page(FilesBO filesBO) {
+        return success(fileService.selectPage(filesBO));
+    }
+
+    @GetMapping("/list")
+    public CommonResult<List<FilesVO>> list(FilesBO filesBO) {
+        return success(fileService.selectList(filesBO));
+    }
+
+    @DeleteMapping("/delete")
+    @SaCheckPermission(value = "system:config:delete")
+    public CommonResult<String> delete(@RequestBody @Valid @NotEmpty(message = "集合不能为空") List<String> ids) {
+        boolean result = fileService.delete(ids);
+        if (!result) {
+            return fail("删除失败!");
+        }
+        return success();
+    }
+
+    @DeleteMapping("/delete/file")
+    @SaCheckPermission(value = "system:config:delete")
+    public CommonResult<String> deleteFile(@RequestBody @Valid @NotEmpty(message = "集合不能为空") List<String> ids) {
+        boolean result = fileService.deleteFile(ids);
+        if (!result) {
+            return fail("删除失败!");
+        }
+        return success();
+    }
+
     @PostMapping("/upload")
-    public CommonResult<String> upload(@RequestPart("files") MultipartFile file) {
-        return success(fileService.upload(configService.getConfValue(ConfigConstants.FILE_ENGINE_KE), file));
+    public CommonResult<String> upload(@RequestPart("file") MultipartFile file) {
+        return success(fileService.upload(configService.getConfValue(ConfigConstants.FILE_ENGINE_KE), file), "");
     }
 
-    @PostMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
-    public CommonResult<Valid> download(@Valid FilesBO filesBO, HttpServletResponse response) throws IOException {
-        fileService.download(filesBO.getFileId(), response);
+    @GetMapping(value = "/download/{id}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
+    public CommonResult<Void> download(@PathVariable String id, HttpServletResponse response) throws IOException {
+        fileService.download(id, response);
         return success();
     }
 }

+ 2 - 2
eco-nexus-core/nexus-core-biz/src/main/java/org/eco/vip/nexus/core/domain/files/Files.java

@@ -20,7 +20,7 @@ import org.eco.vip.orm.domain.BaseEntity;
  */
 @Data
 @EqualsAndHashCode(callSuper = true)
-@Table("system_file_t")
+@Table("system_files_t")
 public class Files extends BaseEntity {
 
     /**
@@ -52,7 +52,7 @@ public class Files extends BaseEntity {
     /**
      * 文件大小kb
      */
-    private Integer sizeKb;
+    private Long sizeKb;
 
     /**
      * 文件压缩后大小

+ 42 - 21
eco-nexus-core/nexus-core-biz/src/main/java/org/eco/vip/nexus/core/service/files/FilesService.java

@@ -8,19 +8,20 @@ package org.eco.vip.nexus.core.service.files;
 
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
 import com.mybatisflex.core.paginate.Page;
 import com.mybatisflex.core.query.QueryWrapper;
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.extern.slf4j.Slf4j;
+import org.eco.vip.core.constant.Constants;
+import org.eco.vip.core.exception.BusinessException;
 import org.eco.vip.core.pojo.PageResult;
 import org.eco.vip.core.utils.FileDownloadUtils;
 import org.eco.vip.core.utils.FileLocalUtils;
 import org.eco.vip.core.utils.FileUtils;
 import org.eco.vip.core.utils.ImgUtils;
-import org.eco.vip.core.utils.IoUtils;
 import org.eco.vip.core.utils.MapstructUtils;
+import org.eco.vip.core.utils.Seqs;
 import org.eco.vip.core.utils.StrUtils;
 import org.eco.vip.nexus.core.domain.files.Files;
 import org.eco.vip.nexus.core.domain.files.pojo.FilesBO;
@@ -28,14 +29,15 @@ import org.eco.vip.nexus.core.domain.files.pojo.FilesVO;
 import org.eco.vip.nexus.core.mapper.FilesMapper;
 import org.eco.vip.orm.domain.PageQuery;
 import org.eco.vip.orm.service.BaseService;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.List;
-import java.util.UUID;
 
+import static org.eco.vip.core.utils.IpUtils.getIpAddress;
 import static org.eco.vip.nexus.core.domain.files.table.FilesTableDef.FILES;
 
 /**
@@ -48,6 +50,8 @@ import static org.eco.vip.nexus.core.domain.files.table.FilesTableDef.FILES;
 @Slf4j
 public class FilesService extends BaseService<FilesMapper, Files> implements IFilesService {
 
+    @Value("${server.port:9040}")
+    private Long port;
     @Resource
     private FilesMapper filesMapper;
 
@@ -93,7 +97,21 @@ public class FilesService extends BaseService<FilesMapper, Files> implements IFi
 
     @Override
     public boolean deleteFile(List<String> ids) {
-        return false;
+
+        List<Files> files = filesMapper.selectListByIds(ids);
+        if (ObjectUtil.isEmpty(files)) {
+            return false;
+        }
+        files.forEach(filesInfo -> {
+            try {
+                FileLocalUtils.deleteFile(filesInfo.getStorageUrl());
+            } catch (Exception e) {
+                log.error("文件删除失败:{},路径:{}", filesInfo.getOriginalName(), filesInfo.getStorageUrl(), e);
+                throw new BusinessException("文件删除失败");
+            }
+        });
+        return this.delete(ids);
+
     }
 
     @Override
@@ -115,7 +133,7 @@ public class FilesService extends BaseService<FilesMapper, Files> implements IFi
 
     @Override
     public void download(String id, HttpServletResponse response) throws IOException {
-
+        uniDownload(id, response);
     }
 
     @Override
@@ -131,42 +149,45 @@ public class FilesService extends BaseService<FilesMapper, Files> implements IFi
      * @return 文件id/url
      */
     private String storageFile(String engine, MultipartFile file) {
-        String key = UUID.randomUUID().toString().replace("-", "");
-        String storageUrl = FileLocalUtils.storageFileWithReturnUrl("LocalBucket", key, file);
+        String key = Seqs.getId(Seqs.UPLOAD_SEQ_TYPE);
+        String path = FileUtils.genFilePath(key, file.getOriginalFilename());
+        String storageUrl = FileLocalUtils.storageFileWithReturnUrl(Constants.LOCAL_FILE_BUCKET_KEY, path, file);
         Files filesInfo = new Files();
-        filesInfo.setFileId(key);
         filesInfo.setEngine(engine);
-        filesInfo.setBucket("LocalBucket");
+        filesInfo.setBucket(Constants.LOCAL_FILE_BUCKET_KEY);
         filesInfo.setOriginalName(file.getOriginalFilename());
-        filesInfo.setDownloadUrl(storageUrl);
-        String suffix = ObjectUtil.isNotEmpty(file.getOriginalFilename()) ? StrUtil.subAfter(file.getOriginalFilename(),
-                StrUtil.DOT, true) : null;
+        String suffix = FileUtils.getSuffix(file.getOriginalFilename());
         filesInfo.setFileSuffix(suffix);
-        filesInfo.setSizeKb(12);
+        filesInfo.setSizeKb(file.getSize() / 1024);
         filesInfo.setSizeInfo(FileUtils.readableFileSize(file.getSize()));
-        filesInfo.setFileName(ObjectUtil.isNotEmpty(filesInfo.getFileSuffix()) ? key + StrUtil.DOT + filesInfo.getFileSuffix() : null);
+        filesInfo.setFileName(StrUtils.format("{}_{}", key, file.getOriginalFilename()));
         if (ObjectUtil.isNotEmpty(suffix)) {
             if (isPic(suffix)) {
                 try {
                     filesInfo.setThumbnail(ImgUtils.toBase64DataUri(ImgUtils.scale(ImgUtils.toImage(file.getBytes()),
                             100, 100, null), suffix));
-                } catch (Exception ignored) {
+                } catch (Exception e) {
+                    log.error("图片压缩失败:{}", filesInfo.getOriginalName(), e);
                 }
             }
         }
+        String downloadUrl = Constants.HTTP + getIpAddress() + ":" + port + FileUtils.FILE_SEPARATOR + Constants.LOCAL_FILE_BUCKET_KEY + FileUtils.FILE_SEPARATOR + path;
+        filesInfo.setDownloadUrl(downloadUrl);
         filesInfo.setStorageUrl(storageUrl);
-
         this.save(filesInfo);
-        return "";
+        return filesInfo.getFileId();
     }
 
-    private void unifiedDownload(FilesBO filesBO, HttpServletResponse response, boolean isDownloadAuth) {
-        FilesVO filesVO = this.selectById(filesBO.getFileId());
-        File file = FileUtils.file(filesVO.getStorageUrl());
+    private void uniDownload(String id, HttpServletResponse response) {
+        FilesVO filesVO = this.selectById(id);
+        if (ObjectUtil.isEmpty(filesVO)) {
+            return;
+        }
+        File file = FileLocalUtils.getFileByBucketNameAndPath(filesVO.getStorageUrl());
         if (!FileUtil.exist(file)) {
             return;
         }
-        FileDownloadUtils.download(file.getName(), IoUtils.readBytes(FileUtil.getInputStream(file)), response);
+        FileDownloadUtils.download(file, response);
     }
 
     /**

+ 2 - 2
eco-start/src/main/resources/db/mysql/V1_0_0_1__sys-init-ddl.sql

@@ -54,8 +54,8 @@ VALUES ('71718796894000126', '7daa6e9b-8876-4918-8a12-b68cbfcdc680', 'pc', 'eco-
 -- ----------------------------
 -- Table structure for system_file_t
 -- ----------------------------
-DROP TABLE IF EXISTS `system_file_t`;
-CREATE TABLE `system_file_t`
+DROP TABLE IF EXISTS `system_files_t`;
+CREATE TABLE `system_files_t`
 (
     `file_id`       varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NOT NULL COMMENT '唯一标识',
     `engine`        varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '引擎名',