瀏覽代碼

以 oss1 仓库 master 分支的 25a9727221 版本为起点

reghao 2 年之前
當前提交
3c3aa5d6d8
共有 100 個文件被更改,包括 4219 次插入0 次删除
  1. 6 0
      .gitignore
  2. 45 0
      oss-api/pom.xml
  3. 54 0
      oss-api/src/main/java/cn/reghao/oss/api/constant/ChannelAction.java
  4. 56 0
      oss-api/src/main/java/cn/reghao/oss/api/constant/ObjectScope.java
  5. 61 0
      oss-api/src/main/java/cn/reghao/oss/api/constant/ObjectType.java
  6. 15 0
      oss-api/src/main/java/cn/reghao/oss/api/constant/SupportedMedia.java
  7. 62 0
      oss-api/src/main/java/cn/reghao/oss/api/constant/UploadChannel.java
  8. 11 0
      oss-api/src/main/java/cn/reghao/oss/api/constant/VideoUrlType.java
  9. 20 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/ChannelDto.java
  10. 28 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/DeleteFile.java
  11. 20 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/DirProp.java
  12. 19 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/DownloadUrl.java
  13. 23 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/FileInfo.java
  14. 23 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/FileProp.java
  15. 34 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectInfo.java
  16. 25 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectMeta.java
  17. 21 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectPrefix.java
  18. 22 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectUrl.java
  19. 20 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/OssPayload.java
  20. 21 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/ServerInfo.java
  21. 21 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/media/AudioInfo.java
  22. 21 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/media/AudioUrl.java
  23. 24 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/media/ImageUrlDto.java
  24. 28 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/media/VideoInfo.java
  25. 22 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/media/VideoUrlDto.java
  26. 13 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/ChannelService.java
  27. 18 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/ObjectService.java
  28. 11 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/OssServerService.java
  29. 15 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/PermissionService.java
  30. 17 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/disk/DiskService.java
  31. 22 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/disk/FileService.java
  32. 12 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/disk/TrashService.java
  33. 15 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/media/AudioFileService.java
  34. 12 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/media/ConvertService.java
  35. 19 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/media/ImageFileService.java
  36. 19 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/media/MediaScopeService.java
  37. 18 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/media/VideoFileService.java
  38. 34 0
      oss-api/src/main/java/cn/reghao/oss/api/rest/UploadFilePart.java
  39. 34 0
      oss-api/src/main/java/cn/reghao/oss/api/rest/UploadFileRet.java
  40. 27 0
      oss-api/src/main/java/cn/reghao/oss/api/rest/UploadPrepare.java
  41. 21 0
      oss-api/src/main/java/cn/reghao/oss/api/rest/UploadPrepareRet.java
  42. 22 0
      oss-api/src/main/java/cn/reghao/oss/api/rest/UploadedPart.java
  43. 39 0
      oss-common/pom.xml
  44. 34 0
      oss-common/src/main/java/cn/reghao/oss/common/UploadFilePart.java
  45. 30 0
      oss-common/src/main/java/cn/reghao/oss/common/UploadFileRet.java
  46. 27 0
      oss-common/src/main/java/cn/reghao/oss/common/UploadPrepare.java
  47. 21 0
      oss-common/src/main/java/cn/reghao/oss/common/UploadPrepareRet.java
  48. 22 0
      oss-common/src/main/java/cn/reghao/oss/common/UploadedPart.java
  49. 49 0
      oss-common/src/main/java/cn/reghao/oss/common/constant/UploadChannel.java
  50. 60 0
      oss-sdk/pom.xml
  51. 172 0
      oss-sdk/src/main/java/cn/reghao/oss/sdk/MultiPartBodyPublisher.java
  52. 145 0
      oss-sdk/src/main/java/cn/reghao/oss/sdk/ObjectGetService.java
  53. 187 0
      oss-sdk/src/main/java/cn/reghao/oss/sdk/ObjectMultipartUploadService.java
  54. 138 0
      oss-sdk/src/main/java/cn/reghao/oss/sdk/ObjectUploadService.java
  55. 43 0
      oss-sdk/src/test/java/ObjectTest.java
  56. 7 0
      oss-store/Dockerfile
  57. 6 0
      oss-store/README.md
  58. 17 0
      oss-store/bin/oss.yml
  59. 5 0
      oss-store/bin/shutdown.sh
  60. 6 0
      oss-store/bin/start.sh
  61. 190 0
      oss-store/pom.xml
  62. 11 0
      oss-store/src/main/java/cn/reghao/oss/store/OssStoreApplication.java
  63. 40 0
      oss-store/src/main/java/cn/reghao/oss/store/config/CacheConfig.java
  64. 23 0
      oss-store/src/main/java/cn/reghao/oss/store/config/OssProperties.java
  65. 68 0
      oss-store/src/main/java/cn/reghao/oss/store/config/mysql/DataSourceConfig.java
  66. 83 0
      oss-store/src/main/java/cn/reghao/oss/store/config/mysql/PageListInterceptor.java
  67. 77 0
      oss-store/src/main/java/cn/reghao/oss/store/config/spring/FileLifecycle.java
  68. 61 0
      oss-store/src/main/java/cn/reghao/oss/store/config/web/PutMessageConverter.java
  69. 72 0
      oss-store/src/main/java/cn/reghao/oss/store/config/web/WebConfig.java
  70. 106 0
      oss-store/src/main/java/cn/reghao/oss/store/controller/ObjectGetController.java
  71. 51 0
      oss-store/src/main/java/cn/reghao/oss/store/controller/ObjectMultipartUploadController.java
  72. 130 0
      oss-store/src/main/java/cn/reghao/oss/store/controller/ObjectUploadController.java
  73. 20 0
      oss-store/src/main/java/cn/reghao/oss/store/db/mapper/AudioFileMapper.java
  74. 23 0
      oss-store/src/main/java/cn/reghao/oss/store/db/mapper/DataBlockMapper.java
  75. 56 0
      oss-store/src/main/java/cn/reghao/oss/store/db/mapper/FileMetaMapper.java
  76. 13 0
      oss-store/src/main/java/cn/reghao/oss/store/db/mapper/FileTypeMapper.java
  77. 23 0
      oss-store/src/main/java/cn/reghao/oss/store/db/mapper/ImageFileMapper.java
  78. 22 0
      oss-store/src/main/java/cn/reghao/oss/store/db/mapper/VideoFileMapper.java
  79. 40 0
      oss-store/src/main/java/cn/reghao/oss/store/db/repository/AudioRepository.java
  80. 96 0
      oss-store/src/main/java/cn/reghao/oss/store/db/repository/ImageRepository.java
  81. 115 0
      oss-store/src/main/java/cn/reghao/oss/store/db/repository/ObjectRepository.java
  82. 63 0
      oss-store/src/main/java/cn/reghao/oss/store/db/repository/VideoRepository.java
  83. 62 0
      oss-store/src/main/java/cn/reghao/oss/store/exception/ControllerExceptionHandler.java
  84. 43 0
      oss-store/src/main/java/cn/reghao/oss/store/exception/FilterExceptionHandler.java
  85. 69 0
      oss-store/src/main/java/cn/reghao/oss/store/inerceptor/AccessLogInterceptor.java
  86. 40 0
      oss-store/src/main/java/cn/reghao/oss/store/inerceptor/MutableHttpServletRequest.java
  87. 47 0
      oss-store/src/main/java/cn/reghao/oss/store/inerceptor/TokenFilter.java
  88. 15 0
      oss-store/src/main/java/cn/reghao/oss/store/model/dto/ContentRange.java
  89. 18 0
      oss-store/src/main/java/cn/reghao/oss/store/model/dto/PathUrl.java
  90. 35 0
      oss-store/src/main/java/cn/reghao/oss/store/model/po/AudioFile.java
  91. 38 0
      oss-store/src/main/java/cn/reghao/oss/store/model/po/DataBlock.java
  92. 78 0
      oss-store/src/main/java/cn/reghao/oss/store/model/po/FileMeta.java
  93. 20 0
      oss-store/src/main/java/cn/reghao/oss/store/model/po/FileType.java
  94. 46 0
      oss-store/src/main/java/cn/reghao/oss/store/model/po/ImageFile.java
  95. 66 0
      oss-store/src/main/java/cn/reghao/oss/store/model/po/VideoFile.java
  96. 13 0
      oss-store/src/main/java/cn/reghao/oss/store/model/vo/ImageObject.java
  97. 17 0
      oss-store/src/main/java/cn/reghao/oss/store/model/vo/ObjectProp.java
  98. 36 0
      oss-store/src/main/java/cn/reghao/oss/store/model/vo/ObjectResult.java
  99. 31 0
      oss-store/src/main/java/cn/reghao/oss/store/rpc/ChannelServiceImpl.java
  100. 123 0
      oss-store/src/main/java/cn/reghao/oss/store/rpc/ObjectServiceImpl.java

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+.idea/
+*target*/
+*logs*/
+*.jar
+*.iml
+console.log

+ 45 - 0
oss-api/pom.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>cn.reghao.oss</groupId>
+    <artifactId>oss-api</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.reghao.jutil</groupId>
+            <artifactId>jdk</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.6</version>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.validation</groupId>
+            <artifactId>validation-api</artifactId>
+            <version>2.0.1.Final</version>
+        </dependency>
+    </dependencies>
+
+    <distributionManagement>
+        <repository>
+            <id>maven-local-hosted</id>
+            <url>http://nexus.reghao.cn/repository/maven-local-hosted/</url>
+        </repository>
+    </distributionManagement>
+</project>

+ 54 - 0
oss-api/src/main/java/cn/reghao/oss/api/constant/ChannelAction.java

@@ -0,0 +1,54 @@
+package cn.reghao.oss.api.constant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2023-10-18 13:15:45
+ */
+public enum ChannelAction {
+    access(1, "访问"),
+    download(2, "下载"),
+    upload(3, "上传"),
+    delete(4, "删除");
+
+    private final int code;
+    private final String desc;
+
+    private static Map<Integer, String> descMap = new HashMap<>();
+    static {
+        for (ChannelAction scope : ChannelAction.values()) {
+            descMap.put(scope.code, scope.desc);
+        }
+    }
+
+    ChannelAction(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public String getName() {
+        return this.name();
+    }
+
+    /**
+     * 提供给 @ValidEnum 调用
+     *
+     * @param
+     * @return
+     * @date 2023-10-11 14:44:42
+     */
+    public int getValue() {
+        return this.code;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    // TODO 第一次调用时会初始化 descMap
+    public static String getDescByCode(int code) {
+        return descMap.get(code);
+    }
+}

+ 56 - 0
oss-api/src/main/java/cn/reghao/oss/api/constant/ObjectScope.java

@@ -0,0 +1,56 @@
+package cn.reghao.oss.api.constant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 描述资源可被看见的范围
+ *
+ * @author reghao
+ * @date 2023-05-19 18:15:45
+ */
+public enum ObjectScope {
+    PRIVATE(1, "本人可见"),
+    PUBLIC(2, "所有人可见"),
+    PROTECT(3, "VIP 可见"),
+    FRIEND(4, "验证码可见");
+
+    private final int code;
+    private final String desc;
+
+    private static Map<Integer, String> descMap = new HashMap<>();
+    static {
+        for (ObjectScope scope : ObjectScope.values()) {
+            descMap.put(scope.code, scope.desc);
+        }
+    }
+
+    ObjectScope(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public String getName() {
+        return this.name();
+    }
+
+    /**
+     * 提供给 @ValidEnum 调用
+     *
+     * @param
+     * @return
+     * @date 2023-10-11 14:44:42
+     */
+    public int getValue() {
+        return this.code;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    // TODO 第一次调用时会初始化 descMap
+    public static String getDescByCode(int code) {
+        return descMap.get(code);
+    }
+}

+ 61 - 0
oss-api/src/main/java/cn/reghao/oss/api/constant/ObjectType.java

@@ -0,0 +1,61 @@
+package cn.reghao.oss.api.constant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2023-06-13 15:09:09
+ */
+public enum ObjectType {
+    Dir(1000, ""),
+    Image(1001, "图片"),
+    Video(1002, "视频"),
+    Audio(1003, "音频"),
+    Text(1004, "文本"),
+    Other(1005, "二进制");
+
+    private final int code;
+    private final String desc;
+    ObjectType(int code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    private static Map<Integer, ObjectType> map = new HashMap<>();
+    static {
+        for (ObjectType type : ObjectType.values()) {
+            map.put(type.code, type);
+        }
+    }
+
+    private static Map<Integer, String> descMap = new HashMap<>();
+    static {
+        for (ObjectType objectType : ObjectType.values()) {
+            descMap.put(objectType.code, objectType.desc);
+        }
+    }
+
+    public static ObjectType getByCode(int code) {
+        return map.get(code);
+    }
+
+    /**
+     * 提供给 @ValidEnum 调用
+     *
+     * @param
+     * @return
+     * @date 2023-10-11 14:44:42
+     */
+    public int getValue() {
+        return this.code;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public static String getDescByCode(int code) {
+        return descMap.get(code);
+    }
+}

+ 15 - 0
oss-api/src/main/java/cn/reghao/oss/api/constant/SupportedMedia.java

@@ -0,0 +1,15 @@
+package cn.reghao.oss.api.constant;
+
+import java.util.Set;
+
+/**
+ * 系统支持的媒体文件格式
+ *
+ * @author reghao
+ * @date 2023-10-09 10:34:42
+ */
+public class SupportedMedia {
+    public static final Set<String> videoCodecs = Set.of("h264");
+    public static final Set<String> audioCodecs = Set.of("aac", "mp3");
+    public static final Set<String> imageFormats = Set.of("jpeg", "jpg", "webp", "gif", "png");
+}

+ 62 - 0
oss-api/src/main/java/cn/reghao/oss/api/constant/UploadChannel.java

@@ -0,0 +1,62 @@
+package cn.reghao.oss.api.constant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2023-05-23 10:43:25
+ */
+public enum UploadChannel {
+    // 网盘上传(单个文件最大 20GiB)
+    disk(1, "file/", 1024L*1024*1024*20),
+    // 视频上传(单个文件最大 10GiB)
+    video(2, "video/playback/", 1024L*1024*1024*10),
+    // 音频上传(单个文件最大 1GiB)
+    audio(3, "audio/playback/", 1024L*1024*1024),
+    // 用户头像上传(单个文件最大 2MiB)
+    avatar(4, "image/a/", 1024L*1024*2),
+    // 图片上传(单个文件最大 10MiB)
+    image(5, "image/i/", 1024L*1024*10),
+    // 用户状态照片上传(单个文件最大 100MiB)
+    photo(6, "image/p/", 1024L*1024*100),
+    img(7, "img/", 1024L*1024*10);
+
+    private final int code;
+    private final String prefix;
+    private final long maxSize;
+    UploadChannel(int code, String prefix, long maxSize) {
+        this.code = code;
+        this.prefix = prefix;
+        this.maxSize = maxSize;
+    }
+
+    private static Map<Integer, UploadChannel> map = new HashMap<>();
+    private static Map<String, UploadChannel> map1 = new HashMap<>();
+    static {
+        for (UploadChannel channel : UploadChannel.values()) {
+            map.put(channel.code, channel);
+            map1.put(channel.prefix, channel);
+        }
+    }
+
+    public static UploadChannel getUploadChannel(int code) {
+        return map.get(code);
+    }
+
+    public static UploadChannel getUploadChannel(String prefix) {
+        return map1.get(prefix);
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public long getMaxSize() {
+        return maxSize;
+    }
+}

+ 11 - 0
oss-api/src/main/java/cn/reghao/oss/api/constant/VideoUrlType.java

@@ -0,0 +1,11 @@
+package cn.reghao.oss.api.constant;
+
+/**
+ * 视频 URL 类型
+ *
+ * @author reghao
+ * @date 2021-12-28 15:49:19
+ */
+public enum VideoUrlType {
+    mp4, hls, dash, flv
+}

+ 20 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/ChannelDto.java

@@ -0,0 +1,20 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-06-02 14:38:47
+ */
+@AllArgsConstructor
+@Getter
+public class ChannelDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String channelPrefix;
+    private int channelId;
+    private String channelName;
+}

+ 28 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/DeleteFile.java

@@ -0,0 +1,28 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-05-15 17:06:54
+ */
+@Setter
+@Getter
+public class DeleteFile implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String fileId;
+    private String filename;
+    private int type;
+    private String icon;
+    private long size;
+    private String updateTime;
+    private int leftDays;
+
+    public DeleteFile() {
+        this.leftDays = 10;
+    }
+}

+ 20 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/DirProp.java

@@ -0,0 +1,20 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-05-19 11:36:50
+ */
+@AllArgsConstructor
+@Getter
+public class DirProp implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private long totalSubDirs;
+    private long totalFiles;
+    private long totalSize;
+}

+ 19 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/DownloadUrl.java

@@ -0,0 +1,19 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-08-24 15:01:43
+ */
+@AllArgsConstructor
+@Getter
+public class DownloadUrl implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String url;
+    private String token;
+}

+ 23 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/FileInfo.java

@@ -0,0 +1,23 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-05-15 17:06:54
+ */
+@Setter
+@Getter
+public class FileInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String fileId;
+    private String filename;
+    private int type;
+    private String icon;
+    private long size;
+    private String updateTime;
+}

+ 23 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/FileProp.java

@@ -0,0 +1,23 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-05-19 10:46:23
+ */
+@AllArgsConstructor
+@Getter
+public class FileProp implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String path;
+    private String filename;
+    private Long size;
+    private String contentType;
+    private String sha256sum;
+    private String url;
+}

+ 34 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectInfo.java

@@ -0,0 +1,34 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-11-29 18:11:13
+ */
+@Setter
+@Getter
+public class ObjectInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String objectId;
+    private String objectName;
+    private int fileType;
+    private String filename;
+    private long size;
+    private int width;
+    private int height;
+
+    public ObjectInfo(String objectId, String objectName, int fileType, String filename, long size) {
+        this.objectId = objectId;
+        this.objectName = objectName;
+        this.fileType = fileType;
+        this.filename = filename;
+        this.size = size;
+        this.width = 0;
+        this.height = 0;
+    }
+}

+ 25 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectMeta.java

@@ -0,0 +1,25 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-04-30 12:17:28
+ */
+@Setter
+@Getter
+public class ObjectMeta implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String contentId;
+    private String objectName;
+    private String objectId;
+    private String absolutePath;
+    private long size;
+    private String contentType;
+    private int scope;
+    private long uploadBy;
+}

+ 21 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectPrefix.java

@@ -0,0 +1,21 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.Getter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-06-02 17:37:49
+ */
+@Getter
+public class ObjectPrefix implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @NotBlank
+    private String prefix;
+    @NotNull
+    private Integer scope;
+}

+ 22 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectUrl.java

@@ -0,0 +1,22 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-11-29 16:58:06
+ */
+@AllArgsConstructor
+@Getter
+public class ObjectUrl implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String objectId;
+    private String objectName;
+    private int fileType;
+    private String url;
+    private String signedUrl;
+}

+ 20 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/OssPayload.java

@@ -0,0 +1,20 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-08-23 10:26:01
+ */
+@AllArgsConstructor
+@Getter
+public class OssPayload implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String action;
+    private int channelId;
+    private Long userId;
+}

+ 21 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/ServerInfo.java

@@ -0,0 +1,21 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-08-23 11:41:55
+ */
+@AllArgsConstructor
+@Getter
+public class ServerInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String ossUrl;
+    private int channelId;
+    private long maxSize;
+    private String token;
+}

+ 21 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/media/AudioInfo.java

@@ -0,0 +1,21 @@
+package cn.reghao.oss.api.dto.media;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-08-28 17:00:56
+ */
+@NoArgsConstructor
+@Getter
+public class AudioInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String audioFileId;
+    private int duration;
+    private String codec;
+    private String url;
+}

+ 21 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/media/AudioUrl.java

@@ -0,0 +1,21 @@
+package cn.reghao.oss.api.dto.media;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-10-12 15:07:44
+ */
+@Setter
+@Getter
+public class AudioUrl implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String audioFileId;
+    private String codec;
+    private Long bitRate;
+    private String url;
+}

+ 24 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/media/ImageUrlDto.java

@@ -0,0 +1,24 @@
+package cn.reghao.oss.api.dto.media;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-08-28 15:50:38
+ */
+@Setter
+@Getter
+public class ImageUrlDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String imageFileId;
+    private String originalUrl;
+    private String thumbnailUrl;
+
+    public ImageUrlDto(String imageFileId) {
+        this.imageFileId = imageFileId;
+    }
+}

+ 28 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/media/VideoInfo.java

@@ -0,0 +1,28 @@
+package cn.reghao.oss.api.dto.media;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-01-11 10:41:53
+ */
+@NoArgsConstructor
+@Setter
+@Getter
+public class VideoInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String videoFileId;
+    // 单位秒
+    private Integer duration;
+    private Boolean horizontal;
+    private String quality;
+    private String videoCodec;
+    private String audioCodec;
+    private String urlType;
+    private String url;
+}

+ 22 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/media/VideoUrlDto.java

@@ -0,0 +1,22 @@
+package cn.reghao.oss.api.dto.media;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-01-10 09:51:53
+ */
+@Getter
+@Setter
+public class VideoUrlDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String type;
+    private String url;
+    private int width;
+    private int height;
+    private String quality;
+}

+ 13 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/ChannelService.java

@@ -0,0 +1,13 @@
+package cn.reghao.oss.api.iface;
+
+import cn.reghao.oss.api.dto.ChannelDto;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-06-02 10:27:58
+ */
+public interface ChannelService {
+    List<ChannelDto> getChannels();
+}

+ 18 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/ObjectService.java

@@ -0,0 +1,18 @@
+package cn.reghao.oss.api.iface;
+
+import cn.reghao.oss.api.dto.*;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-05-28 23:25:06
+ */
+public interface ObjectService {
+    void putObjectPrefix(ObjectPrefix objectPrefix);
+    List<String> getObjectPrefix();
+    ObjectMeta getObject(String objectName);
+    DownloadUrl getDownloadUrl(String objectId, int channelId, long userId);
+    ObjectInfo getObjectInfo(String objectId);
+    ObjectUrl getObjectUrl(String objectId);
+}

+ 11 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/OssServerService.java

@@ -0,0 +1,11 @@
+package cn.reghao.oss.api.iface;
+
+import cn.reghao.oss.api.dto.ServerInfo;
+
+/**
+ * @author reghao
+ * @date 2023-08-01 14:51:50
+ */
+public interface OssServerService {
+    ServerInfo getServerInfo(long userId, int channelId);
+}

+ 15 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/PermissionService.java

@@ -0,0 +1,15 @@
+package cn.reghao.oss.api.iface;
+
+import java.util.Set;
+
+/**
+ * @author reghao
+ * @date 2023-06-02 10:06:21
+ */
+public interface PermissionService {
+    void setDirPermission(String prefix, int scope);
+    void getDirPermission(String prefix);
+    void setVideoPermission(String videoFileId, int scope);
+    void setAudioPermission(String audioFileId, int scope);
+    void setImagesPermission(Set<String> imageFileIds, int scope);
+}

+ 17 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/disk/DiskService.java

@@ -0,0 +1,17 @@
+package cn.reghao.oss.api.iface.disk;
+
+import cn.reghao.oss.api.dto.FileInfo;
+import cn.reghao.jutil.jdk.db.PageList;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-05-22 15:57:51
+ */
+public interface DiskService {
+    PageList<FileInfo> getFileList(String pid, int pageNumber);
+    PageList<FileInfo> getFileCard(String pid, int pageNumber);
+    List<FileInfo> getDirectories(String pid);
+    PageList<FileInfo> search(String keyword, int pageNumber);
+}

+ 22 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/disk/FileService.java

@@ -0,0 +1,22 @@
+package cn.reghao.oss.api.iface.disk;
+
+import cn.reghao.oss.api.dto.DirProp;
+import cn.reghao.oss.api.dto.FileProp;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-05-18 19:23:07
+ */
+public interface FileService {
+    String checkFilename(String pid, String filename);
+    void createDirectory(String pid, String filename);
+    String createFile(String pid, String filename, String content);
+    void copyFile(List<String> fileIds, String toPid);
+    void moveFile(List<String> fileIds, String toPid);
+    void renameFile(String fileId, String newFilename);
+    void deleteFiles(List<String> fileIds);
+    FileProp getFileProp(String fileId);
+    DirProp getDirProp(String fileId);
+}

+ 12 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/disk/TrashService.java

@@ -0,0 +1,12 @@
+package cn.reghao.oss.api.iface.disk;
+
+import cn.reghao.oss.api.dto.DeleteFile;
+import cn.reghao.jutil.jdk.db.PageList;
+
+/**
+ * @author reghao
+ * @date 2023-05-22 15:57:57
+ */
+public interface TrashService {
+    PageList<DeleteFile> getTrashList(int pageNumber, int pageSize);
+}

+ 15 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/media/AudioFileService.java

@@ -0,0 +1,15 @@
+package cn.reghao.oss.api.iface.media;
+
+import cn.reghao.oss.api.dto.media.AudioInfo;
+import cn.reghao.oss.api.dto.media.AudioUrl;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-08-28 17:00:38
+ */
+public interface AudioFileService {
+    AudioInfo getAudioInfo(String audioFileId);
+    List<AudioUrl> getAudioUrls(String audioFileId, long loginUser);
+}

+ 12 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/media/ConvertService.java

@@ -0,0 +1,12 @@
+package cn.reghao.oss.api.iface.media;
+
+/**
+ * 音视频转码服务
+ *
+ * @author reghao
+ * @date 2023-10-08 16:53:33
+ */
+public interface ConvertService {
+    void convertVideo(String videoFileId);
+    void convertAudio(String audioFileId);
+}

+ 19 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/media/ImageFileService.java

@@ -0,0 +1,19 @@
+package cn.reghao.oss.api.iface.media;
+
+import cn.reghao.oss.api.dto.media.ImageUrlDto;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author reghao
+ * @date 2023-08-28 15:47:43
+ */
+public interface ImageFileService {
+    void deleteByObjectNames(List<String> objectNames);
+    void deleteByImageFileIds(List<String> imageFileIds);
+    ImageUrlDto getImageUrl(int channelId, String imageFileId);
+    ImageUrlDto getImageUrl(String imageFileId, long loginUser, int channelId);
+    List<ImageUrlDto> getImageUrls(Set<String> imageFileIds);
+    String getSignedUrl(String url, long loginUser, int channelId);
+}

+ 19 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/media/MediaScopeService.java

@@ -0,0 +1,19 @@
+package cn.reghao.oss.api.iface.media;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author reghao
+ * @date 2023-10-12 15:06:21
+ */
+public interface MediaScopeService {
+    @Deprecated
+    void setVideoScope(String videoFileId, int scope);
+    @Deprecated
+    void setAudioScope(String audioFileId, int scope);
+    @Deprecated
+    void setImagesScope(Set<String> imageFileIds, int scope);
+    void setObjectScope(int scope, String objectId, int contentType);
+    void setObjectsScope(int scope, List<String> objectIds, int contentType);
+}

+ 18 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/media/VideoFileService.java

@@ -0,0 +1,18 @@
+package cn.reghao.oss.api.iface.media;
+
+import cn.reghao.oss.api.dto.media.VideoInfo;
+import cn.reghao.oss.api.dto.media.VideoUrlDto;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-08-28 15:46:17
+ */
+public interface VideoFileService {
+    void deleteVideoFile(String videoFileId);
+    VideoInfo getVideoInfo(String videoFileId);
+    List<VideoUrlDto> getVideoUrls(String videoFileId, long loginUser);
+    LocalDateTime getCreateTime(String videoFileId);
+}

+ 34 - 0
oss-api/src/main/java/cn/reghao/oss/api/rest/UploadFilePart.java

@@ -0,0 +1,34 @@
+package cn.reghao.oss.api.rest;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2022-04-25 10:42:38
+ */
+@AllArgsConstructor
+@Getter
+@Setter
+public class UploadFilePart implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private int channelId;
+    // 文件标识
+    private String identifier;
+    private String filename;
+    private String relativePath;
+    // 文件大小
+    private Long totalSize;
+    // 分片文件大小
+    private Integer chunkSize;
+    // 分片文件数量
+    private Long totalChunks;
+    // 当前分片文件索引
+    private Long chunkNumber;
+    // 当前分片文件大小
+    private Integer currentChunkSize;
+}

+ 34 - 0
oss-api/src/main/java/cn/reghao/oss/api/rest/UploadFileRet.java

@@ -0,0 +1,34 @@
+package cn.reghao.oss.api.rest;
+
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-05-23 11:11:32
+ */
+@Getter
+public class UploadFileRet implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private final String uploadId;
+    private String url;
+    private final boolean merged;
+
+    public UploadFileRet(String uploadId) {
+        this.uploadId = uploadId;
+        this.url = null;
+        this.merged = false;
+    }
+
+    public UploadFileRet(String uploadId, String url) {
+        this.uploadId = uploadId;
+        this.url = url;
+        this.merged = true;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+}

+ 27 - 0
oss-api/src/main/java/cn/reghao/oss/api/rest/UploadPrepare.java

@@ -0,0 +1,27 @@
+package cn.reghao.oss.api.rest;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * 分片文件
+ *
+ * @author reghao
+ * @date 2021-11-23 10:23:00
+ */
+@Setter
+@Getter
+public class UploadPrepare implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @NotBlank
+    private String filename;
+    @NotNull
+    private Long size;
+    @NotBlank
+    private String sha256sum;
+}

+ 21 - 0
oss-api/src/main/java/cn/reghao/oss/api/rest/UploadPrepareRet.java

@@ -0,0 +1,21 @@
+package cn.reghao.oss.api.rest;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2022-04-21 09:27:55
+ */
+@AllArgsConstructor
+@Getter
+public class UploadPrepareRet implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String uploadId;
+    private long splitSize;
+    private boolean exist;
+    private String url;
+}

+ 22 - 0
oss-api/src/main/java/cn/reghao/oss/api/rest/UploadedPart.java

@@ -0,0 +1,22 @@
+package cn.reghao.oss.api.rest;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-05-24 09:23:25
+ */
+@Setter
+@Getter
+public class UploadedPart implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private boolean skipUpload;
+    private boolean needMerge;
+    private List<String> uploaded;
+    private String url;
+}

+ 39 - 0
oss-common/pom.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>cn.reghao.oss</groupId>
+    <artifactId>oss-common</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.6</version>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.validation</groupId>
+            <artifactId>validation-api</artifactId>
+            <version>2.0.1.Final</version>
+        </dependency>
+    </dependencies>
+
+    <distributionManagement>
+        <repository>
+            <id>maven-local-hosted</id>
+            <url>http://nexus.reghao.cn/repository/maven-local-hosted/</url>
+        </repository>
+    </distributionManagement>
+</project>

+ 34 - 0
oss-common/src/main/java/cn/reghao/oss/common/UploadFilePart.java

@@ -0,0 +1,34 @@
+package cn.reghao.oss.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2022-04-25 10:42:38
+ */
+@AllArgsConstructor
+@Getter
+@Setter
+public class UploadFilePart implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private int channelId;
+    // 文件标识
+    private String identifier;
+    private String filename;
+    private String relativePath;
+    // 文件大小
+    private Long totalSize;
+    // 分片文件大小
+    private Integer chunkSize;
+    // 分片文件数量
+    private Long totalChunks;
+    // 当前分片文件索引
+    private Long chunkNumber;
+    // 当前分片文件大小
+    private Integer currentChunkSize;
+}

+ 30 - 0
oss-common/src/main/java/cn/reghao/oss/common/UploadFileRet.java

@@ -0,0 +1,30 @@
+package cn.reghao.oss.common;
+
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-05-23 11:11:32
+ */
+@Getter
+public class UploadFileRet implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private final String uploadId;
+    private final String url;
+    private final boolean merged;
+
+    public UploadFileRet(String uploadId) {
+        this.uploadId = uploadId;
+        this.url = null;
+        this.merged = false;
+    }
+
+    public UploadFileRet(String uploadId, String url) {
+        this.uploadId = uploadId;
+        this.url = url;
+        this.merged = true;
+    }
+}

+ 27 - 0
oss-common/src/main/java/cn/reghao/oss/common/UploadPrepare.java

@@ -0,0 +1,27 @@
+package cn.reghao.oss.common;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * 分片文件
+ *
+ * @author reghao
+ * @date 2021-11-23 10:23:00
+ */
+@Setter
+@Getter
+public class UploadPrepare implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @NotBlank
+    private String filename;
+    @NotNull
+    private Long size;
+    @NotBlank
+    private String sha256sum;
+}

+ 21 - 0
oss-common/src/main/java/cn/reghao/oss/common/UploadPrepareRet.java

@@ -0,0 +1,21 @@
+package cn.reghao.oss.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2022-04-21 09:27:55
+ */
+@AllArgsConstructor
+@Getter
+public class UploadPrepareRet implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String uploadId;
+    private long splitSize;
+    private boolean exist;
+    private String url;
+}

+ 22 - 0
oss-common/src/main/java/cn/reghao/oss/common/UploadedPart.java

@@ -0,0 +1,22 @@
+package cn.reghao.oss.common;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-05-24 09:23:25
+ */
+@Setter
+@Getter
+public class UploadedPart implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private boolean skipUpload;
+    private boolean needMerge;
+    private List<String> uploaded;
+    private String url;
+}

+ 49 - 0
oss-common/src/main/java/cn/reghao/oss/common/constant/UploadChannel.java

@@ -0,0 +1,49 @@
+package cn.reghao.oss.common.constant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2023-05-23 10:43:25
+ */
+public enum UploadChannel {
+    // 网盘上传
+    disk(1, "file/"),
+    // 视频上传
+    video(2, "video/playback/"),
+    // 视频封面上传
+    cover(3, "image/cover/"),
+    // 用户头像上传
+    avatar(4, "image/avatar/"),
+    // 用户状态照片上传
+    photo(5, "image/photo/"),
+    // 音频上传
+    audio(6, "audio/playback/");
+
+    private final int code;
+    private final String prefix;
+    UploadChannel(int code, String prefix) {
+        this.code = code;
+        this.prefix = prefix;
+    }
+
+    private static Map<Integer, String> map = new HashMap<>();
+    static {
+        for (UploadChannel channel : UploadChannel.values()) {
+            map.put(channel.code, channel.prefix);
+        }
+    }
+
+    public static String getPrefix(int code) {
+        return map.get(code);
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+}

+ 60 - 0
oss-sdk/pom.xml

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>cn.reghao.oss</groupId>
+    <artifactId>oss-sdk</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.reghao.oss</groupId>
+            <artifactId>oss-api</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.reghao.jutil</groupId>
+            <artifactId>jdk</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.reghao.jutil</groupId>
+            <artifactId>tool</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.6</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+            <version>1.2.3</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.2.3</version>
+        </dependency>
+    </dependencies>
+
+    <distributionManagement>
+        <repository>
+            <id>maven-local-hosted</id>
+            <url>http://nexus.reghao.cn/repository/maven-local-hosted/</url>
+        </repository>
+    </distributionManagement>
+</project>

+ 172 - 0
oss-sdk/src/main/java/cn/reghao/oss/sdk/MultiPartBodyPublisher.java

@@ -0,0 +1,172 @@
+package cn.reghao.oss.sdk;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.net.http.HttpRequest;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.Supplier;
+
+/**
+ * https://stackoverflow.com/questions/46392160/java-9-httpclient-send-a-multipart-form-data-request
+ *
+ * @author reghao
+ * @date 2022-11-22 16:02:02
+ */
+public class MultiPartBodyPublisher {
+    private List<PartsSpecification> partsSpecificationList = new ArrayList<>();
+    private String boundary = UUID.randomUUID().toString();
+
+    public HttpRequest.BodyPublisher build() {
+        if (partsSpecificationList.size() == 0) {
+            throw new IllegalStateException("Must have at least one part to build multipart message.");
+        }
+        addFinalBoundaryPart();
+        return HttpRequest.BodyPublishers.ofByteArrays(PartsIterator::new);
+    }
+
+    public String getBoundary() {
+        return boundary;
+    }
+
+    public MultiPartBodyPublisher addPart(String name, String value) {
+        PartsSpecification newPart = new PartsSpecification();
+        newPart.type = PartsSpecification.TYPE.STRING;
+        newPart.name = name;
+        newPart.value = value;
+        partsSpecificationList.add(newPart);
+        return this;
+    }
+
+    public MultiPartBodyPublisher addPart(String name, Path value) {
+        PartsSpecification newPart = new PartsSpecification();
+        newPart.type = PartsSpecification.TYPE.FILE;
+        newPart.name = name;
+        newPart.path = value;
+        partsSpecificationList.add(newPart);
+        return this;
+    }
+
+    public MultiPartBodyPublisher addPart(String name, Supplier<InputStream> value, String filename, String contentType) {
+        PartsSpecification newPart = new PartsSpecification();
+        newPart.type = PartsSpecification.TYPE.STREAM;
+        newPart.name = name;
+        newPart.stream = value;
+        newPart.filename = filename;
+        newPart.contentType = contentType;
+        partsSpecificationList.add(newPart);
+        return this;
+    }
+
+    private void addFinalBoundaryPart() {
+        PartsSpecification newPart = new PartsSpecification();
+        newPart.type = PartsSpecification.TYPE.FINAL_BOUNDARY;
+        newPart.value = "--" + boundary + "--";
+        partsSpecificationList.add(newPart);
+    }
+
+    static class PartsSpecification {
+
+        public enum TYPE {
+            STRING, FILE, STREAM, FINAL_BOUNDARY
+        }
+
+        TYPE type;
+        String name;
+        String value;
+        Path path;
+        Supplier<InputStream> stream;
+        String filename;
+        String contentType;
+
+    }
+
+    class PartsIterator implements Iterator<byte[]> {
+
+        private Iterator<PartsSpecification> iter;
+        private InputStream currentFileInput;
+
+        private boolean done;
+        private byte[] next;
+
+        PartsIterator() {
+            iter = partsSpecificationList.iterator();
+        }
+
+        @Override
+        public boolean hasNext() {
+            if (done) return false;
+            if (next != null) return true;
+            try {
+                next = computeNext();
+            } catch (IOException e) {
+                throw new UncheckedIOException(e);
+            }
+            if (next == null) {
+                done = true;
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public byte[] next() {
+            if (!hasNext()) throw new NoSuchElementException();
+            byte[] res = next;
+            next = null;
+            return res;
+        }
+
+        private byte[] computeNext() throws IOException {
+            if (currentFileInput == null) {
+                if (!iter.hasNext()) return null;
+                PartsSpecification nextPart = iter.next();
+                if (PartsSpecification.TYPE.STRING.equals(nextPart.type)) {
+                    String part =
+                            "--" + boundary + "\r\n" +
+                                    "Content-Disposition: form-data; name=" + nextPart.name + "\r\n" +
+                                    "Content-Type: text/plain; charset=UTF-8\r\n\r\n" +
+                                    nextPart.value + "\r\n";
+                    return part.getBytes(StandardCharsets.UTF_8);
+                }
+                if (PartsSpecification.TYPE.FINAL_BOUNDARY.equals(nextPart.type)) {
+                    return nextPart.value.getBytes(StandardCharsets.UTF_8);
+                }
+                String filename;
+                String contentType;
+                if (PartsSpecification.TYPE.FILE.equals(nextPart.type)) {
+                    Path path = nextPart.path;
+                    filename = path.getFileName().toString();
+                    contentType = Files.probeContentType(path);
+                    if (contentType == null) contentType = "application/octet-stream";
+                    currentFileInput = Files.newInputStream(path);
+                } else {
+                    filename = nextPart.filename;
+                    contentType = nextPart.contentType;
+                    if (contentType == null) contentType = "application/octet-stream";
+                    currentFileInput = nextPart.stream.get();
+                }
+                String partHeader =
+                        "--" + boundary + "\r\n" +
+                                "Content-Disposition: form-data; name=" + nextPart.name + "; filename=" + filename + "\r\n" +
+                                "Content-Type: " + contentType + "\r\n\r\n";
+                return partHeader.getBytes(StandardCharsets.UTF_8);
+            } else {
+                byte[] buf = new byte[8192];
+                int r = currentFileInput.read(buf);
+                if (r > 0) {
+                    byte[] actualBytes = new byte[r];
+                    System.arraycopy(buf, 0, actualBytes, 0, r);
+                    return actualBytes;
+                } else {
+                    currentFileInput.close();
+                    currentFileInput = null;
+                    return "\r\n".getBytes(StandardCharsets.UTF_8);
+                }
+            }
+        }
+    }
+}

+ 145 - 0
oss-sdk/src/main/java/cn/reghao/oss/sdk/ObjectGetService.java

@@ -0,0 +1,145 @@
+package cn.reghao.oss.sdk;
+
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
+import cn.reghao.oss.api.rest.UploadFileRet;
+import com.google.gson.reflect.TypeToken;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+
+/**
+ * @author reghao
+ * @date 2022-11-21 17:19:04
+ */
+@Slf4j
+public class ObjectGetService {
+    private final String endpoint;
+    private final HttpClient httpClient = HttpClient.newBuilder().build();
+    private final String baseDir = "/opt/tmp/";
+
+    public ObjectGetService(String endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    public void headObject(String objectName) {
+        try {
+            String api = String.format("%s/%s", endpoint, objectName);
+            HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                    .version(HttpClient.Version.HTTP_1_1)
+                    .method("HEAD", HttpRequest.BodyPublishers.noBody())
+                    .build();
+
+            HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public boolean headObject1(String sha256sum) {
+        try {
+            String api = String.format("%s?sha256sum=%s", endpoint, sha256sum);
+            HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                    .version(HttpClient.Version.HTTP_1_1)
+                    .method("HEAD", HttpRequest.BodyPublishers.noBody())
+                    .build();
+
+            HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+            int statusCode = httpResponse.statusCode();
+            if (statusCode == 200) {
+                return true;
+            } else if (statusCode == 404) {
+                return false;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return false;
+    }
+
+    public void getObject(String objectName) {
+        try {
+            String version = "1.0.0";
+            String api = String.format("%s/%s?client=%s", endpoint, objectName, version);
+            HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                    .version(HttpClient.Version.HTTP_1_1)
+                    .GET()
+                    .build();
+
+            HttpResponse<InputStream> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofInputStream());
+            int statusCode = httpResponse.statusCode();
+            if (statusCode == 200) {
+                String localPath = saveFile(httpResponse.body(), objectName);
+                log.info("saved to {}", localPath);
+            } else {
+                log.error("{}", statusCode);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private UploadFileRet getResult(HttpResponse<String> httpResponse) {
+        int statusCode = httpResponse.statusCode();
+        String body = httpResponse.body();
+        if (statusCode != 200) {
+            log.error("{} -> {}", statusCode, body);
+            return null;
+        }
+
+        Type type = new TypeToken<WebResult<UploadFileRet>>(){}.getType();
+        WebResult<UploadFileRet> webResult = JsonConverter.jsonToObject(body, type);
+        if (webResult.getCode() != 0) {
+            log.error("{}", webResult.getMsg());
+            return null;
+        }
+
+        return webResult.getData();
+    }
+
+    public String saveFile(InputStream in, String objectName) {
+        String filePath = baseDir + objectName;
+        File file = new File(filePath);
+        File parentDir = file.getParentFile();
+        try {
+            if (!parentDir.exists()) {
+                FileUtils.forceMkdir(parentDir);
+            }
+
+            FileOutputStream fos = new FileOutputStream(file);
+            // 1MiB
+            int len = 1024*1024*5;
+            byte[] buf = new byte[len];
+            int readLen;
+            while ((readLen = in.read(buf, 0, len)) != -1) {
+                fos.write(buf, 0, readLen);
+            }
+            fos.close();
+            return filePath;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    public static void main(String[] args) {
+        String endpoint = "";
+        ObjectGetService objectGetService = new ObjectGetService(endpoint);
+
+        String objectName = "video/playback/28d0fd95e224499c9f2cf1d98b4551a5.flv";
+        objectGetService.getObject(objectName);
+
+        /*String sha256sum = "1234567890";
+        objectGetService.headObject1(sha256sum);*/
+    }
+}

+ 187 - 0
oss-sdk/src/main/java/cn/reghao/oss/sdk/ObjectMultipartUploadService.java

@@ -0,0 +1,187 @@
+package cn.reghao.oss.sdk;
+
+import cn.reghao.jutil.jdk.http.UploadParam;
+import cn.reghao.jutil.jdk.http.WebRequest;
+import cn.reghao.jutil.jdk.http.WebResponse;
+import cn.reghao.jutil.jdk.io.FilePart;
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.jutil.jdk.security.DigestUtil;
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
+import cn.reghao.jutil.tool.http.DefaultWebRequest;
+import cn.reghao.oss.api.rest.UploadFilePart;
+import cn.reghao.oss.api.rest.UploadFileRet;
+import cn.reghao.oss.api.rest.UploadPrepareRet;
+import cn.reghao.oss.api.rest.UploadedPart;
+import com.google.gson.reflect.TypeToken;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.*;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2023-06-02 16:19:04
+ */
+@Slf4j
+public class ObjectMultipartUploadService {
+    private final String endpoint;
+    private final FilePart filePart = new FilePart();
+    private final HttpClient httpClient = HttpClient.newBuilder().build();
+    private final WebRequest webRequest = new DefaultWebRequest();
+
+    public ObjectMultipartUploadService(String endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    public void create() throws URISyntaxException, IOException, InterruptedException {
+        MultiPartBodyPublisher publisher = new MultiPartBodyPublisher();
+        publisher.addPart("filename", "1.zip")
+                .addPart("size", 1+"")
+                .addPart("sha256sum", "1234567890");
+
+        String authorization = "";
+        String api = endpoint + "/?create";
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .version(HttpClient.Version.HTTP_1_1)
+                .header("Authorization", authorization)
+                .header("Content-Type", "multipart/form-data; boundary=" + publisher.getBoundary())
+                .POST(publisher.build())
+                .build();
+
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        String body = httpResponse.body();
+        Type type = new TypeToken<WebResult<UploadPrepareRet>>(){}.getType();
+        WebResult<UploadPrepareRet> webResult = JsonConverter.jsonToObject(body, type);
+        if (webResult.getCode() != 0) {
+            String errMsg = webResult.getMsg();
+        }
+
+        UploadPrepareRet uploadPrepareRet = webResult.getData();
+        System.out.println();
+    }
+
+    public void get() throws URISyntaxException, IOException, InterruptedException {
+        String authorization = "";
+        String api = endpoint + "/?multipart";
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .version(HttpClient.Version.HTTP_1_1)
+                .header("Authorization", authorization)
+                //.header("Content-Type", "multipart/form-data; boundary=" + publisher.getBoundary())
+                .GET()
+                .build();
+
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        String body = httpResponse.body();
+        Type type = new TypeToken<WebResult<UploadedPart>>(){}.getType();
+        WebResult<UploadedPart> webResult = JsonConverter.jsonToObject(body, type);
+        if (webResult.getCode() != 0) {
+            String errMsg = webResult.getMsg();
+        }
+
+        UploadedPart uploadedPart = webResult.getData();
+    }
+
+    /**
+     * jdk http 实现
+     * 存在 bug: https://bugs.openjdk.org/browse/JDK-8222968
+     *
+     * @param
+     * @return
+     * @date 2023-05-24 14:56:50
+     */
+    public void postObject1(byte[] bytes, UploadFilePart uploadFilePart)
+            throws URISyntaxException, IOException, InterruptedException {
+        MultiPartBodyPublisher publisher = new MultiPartBodyPublisher();
+        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+        publisher.addPart("file", () -> bais, uploadFilePart.getFilename(), "")
+                //.addPart("file", Path.of(""))
+                .addPart("channelId", uploadFilePart.getChannelId()+"")
+                .addPart("identifier", uploadFilePart.getIdentifier())
+                .addPart("filename", uploadFilePart.getFilename())
+                .addPart("relativePath", uploadFilePart.getRelativePath())
+                .addPart("totalSize", uploadFilePart.getTotalSize()+"")
+                .addPart("chunkSize", uploadFilePart.getChunkSize()+"")
+                .addPart("currentChunkSize", uploadFilePart.getCurrentChunkSize()+"")
+                .addPart("totalChunks", uploadFilePart.getTotalChunks()+"")
+                .addPart("chunkNumber", uploadFilePart.getChunkNumber()+"");
+
+        String authorization = "";
+        String api = endpoint + "/?multipart";
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .version(HttpClient.Version.HTTP_1_1)
+                .header("Authorization", authorization)
+                .header("Content-Type", "multipart/form-data; boundary=" + publisher.getBoundary())
+                .POST(publisher.build())
+                .build();
+
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        System.out.println();
+    }
+
+    public void postObject(byte[] bytes, UploadFilePart uploadFilePart) {
+        UploadParam uploadParam = new UploadParam(bytes, "");
+        Map<String, String> params = new HashMap<>();
+        params.put("channelId", uploadFilePart.getChannelId()+"");
+        params.put("identifier", uploadFilePart.getIdentifier());
+        params.put("filename", uploadFilePart.getFilename());
+        params.put("relativePath", uploadFilePart.getRelativePath());
+        params.put("totalSize", uploadFilePart.getTotalSize()+"");
+        params.put("chunkSize", uploadFilePart.getChunkSize()+"");
+        params.put("currentChunkSize", uploadFilePart.getCurrentChunkSize()+"");
+        params.put("totalChunks", uploadFilePart.getTotalChunks()+"");
+        params.put("chunkNumber", uploadFilePart.getChunkNumber()+"");
+        uploadParam.setTextParams(params);
+
+        String api = endpoint + "/?multipart";
+        WebResponse webResponse = webRequest.upload(api, uploadParam);
+        int statusCode = webResponse.getStatusCode();
+        if (statusCode != 200) {
+            log.error("请求失败");
+        }
+
+        String body = webResponse.getBody();
+        Type type = new TypeToken<WebResult<UploadFileRet>>(){}.getType();
+        WebResult<UploadFileRet> webResult = JsonConverter.jsonToObject(body, type);
+        if (webResult.getCode() != 0) {
+            String errMsg = webResult.getMsg();
+        }
+
+        UploadFileRet uploadFileRet = webResult.getData();
+        if (!uploadFileRet.isMerged()) {
+            log.info("继续上传");
+        } else {
+            log.info("分片上传并合并完成");
+        }
+    }
+
+    public void upload(File file, int channelId) throws Exception {
+        String identifier = DigestUtil.sha256sum(new FileInputStream(file));
+        String filename = file.getName();
+        String relativePath = file.getAbsolutePath();
+
+        long totalSize = file.length();
+        // 分段大小在 5MB - 5GB 之间,只有最后一个分段才允许小于 5MB,不可避免的
+        int chunkSize = filePart.getPartSize();
+        long totalChunks = totalSize/chunkSize;
+        if (totalSize % chunkSize != 0) {
+            totalChunks += 1;
+        }
+
+        for (long i = 0; i < totalChunks; i++) {
+            long start = i*chunkSize;
+            byte[] part = filePart.getPart(file.getAbsolutePath(), chunkSize, start);
+            long chunkNumber = i + 1;
+            int currentChunkSize = part.length;
+            UploadFilePart uploadFilePart = new UploadFilePart(channelId, identifier, filename, relativePath,
+                    totalSize, chunkSize, totalChunks, chunkNumber, currentChunkSize);
+            postObject(part, uploadFilePart);
+        }
+    }
+}

+ 138 - 0
oss-sdk/src/main/java/cn/reghao/oss/sdk/ObjectUploadService.java

@@ -0,0 +1,138 @@
+package cn.reghao.oss.sdk;
+
+import cn.reghao.jutil.jdk.http.UploadParam;
+import cn.reghao.jutil.jdk.http.WebRequest;
+import cn.reghao.jutil.jdk.http.WebResponse;
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.jutil.jdk.security.DigestUtil;
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
+import cn.reghao.jutil.tool.http.DefaultWebRequest;
+import cn.reghao.oss.api.rest.UploadFileRet;
+import com.google.gson.reflect.TypeToken;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * @author reghao
+ * @date 2023-06-02 16:19:04
+ */
+@Slf4j
+public class ObjectUploadService {
+    private final String endpoint;
+    private final HttpClient httpClient = HttpClient.newBuilder().build();
+    private final DefaultWebRequest webRequest = new DefaultWebRequest();
+
+    public ObjectUploadService(String endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    public UploadFileRet putObject(File file, int channelId, long userId) throws Exception {
+        String sha256sum = DigestUtil.sha256sum(file.getAbsolutePath());
+        String api = String.format("%s/", endpoint);
+        HttpRequest.Builder builder = HttpRequest.newBuilder(new URI(api))
+                .version(HttpClient.Version.HTTP_1_1)
+                .header("x-content-sha256sum", sha256sum)
+                .header("x-channel-id", channelId+"")
+                .header("x-user-id", userId+"");
+
+        HttpRequest httpRequest = builder.PUT(HttpRequest.BodyPublishers.ofFile(Path.of(file.getAbsolutePath()))).build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        return getResult(httpResponse);
+    }
+
+    public UploadFileRet putObjectWithJdkHttp(InputStream inputStream, int channelId, int userId) throws Exception {
+        String api = String.format("%s/", endpoint);
+        HttpRequest.Builder builder = HttpRequest.newBuilder(new URI(api))
+                .version(HttpClient.Version.HTTP_1_1)
+                .header("x-content-sha256sum", "1234567890")
+                .header("x-channel-id", channelId+"")
+                .header("x-user-id", userId+"");
+
+        BufferedInputStream bis = new BufferedInputStream(inputStream);
+        Supplier<? extends InputStream> streamSupplier = (Supplier<BufferedInputStream>) () -> bis;
+        HttpRequest httpRequest = builder.PUT(HttpRequest.BodyPublishers.ofInputStream(streamSupplier)).build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        return getResult(httpResponse);
+    }
+
+    public UploadFileRet postObjectWithJdkHttp(File file, int channelId, long userId) throws Exception {
+        String sha256sum = DigestUtil.sha256sum(file.getAbsolutePath());
+        MultiPartBodyPublisher publisher = new MultiPartBodyPublisher()
+                .addPart("file", Paths.get(file.getAbsolutePath()))
+                .addPart("channelId", ""+channelId)
+                .addPart("client", "client")
+                .addPart("sha256sum", sha256sum);
+
+        String api = String.format("%s/", endpoint);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .version(HttpClient.Version.HTTP_1_1)
+                .header("x-user-id", userId+"")
+                //.header("authorization", "Bearer 0123456789")
+                .header("content-type", "multipart/form-data; boundary=" + publisher.getBoundary())
+                .POST(publisher.build())
+                .build();
+
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        return getResult(httpResponse);
+    }
+
+    public UploadFileRet postObject(File file, int channelId, long userId) throws Exception {
+        String sha256sum = DigestUtil.sha256sum(file.getAbsolutePath());
+        Map<String, String> map = new HashMap<>();
+        map.put("channelId", ""+channelId);
+        map.put("client", "client");
+        map.put("userId", ""+userId);
+        map.put("sha256sum", sha256sum);
+        UploadParam uploadParam = new UploadParam(file, map);
+
+        String api = String.format("%s/", endpoint);
+        WebResponse webResponse = webRequest.upload(api, uploadParam);
+        int statusCode = webResponse.getStatusCode();
+        String body = webResponse.getBody();
+        if (statusCode != 200) {
+            String errMsg = String.format("%s -> %s", statusCode, body);
+            throw new Exception(errMsg);
+        }
+
+        Type type = new TypeToken<WebResult<UploadFileRet>>(){}.getType();
+        WebResult<UploadFileRet> webResult = JsonConverter.jsonToObject(body, type);
+        if (webResult.getCode() != 0) {
+            throw new Exception(webResult.getMsg());
+        }
+
+        return webResult.getData();
+    }
+
+    private UploadFileRet getResult(HttpResponse<String> httpResponse) throws Exception {
+        int statusCode = httpResponse.statusCode();
+        String body = httpResponse.body();
+        if (statusCode != 200) {
+            String errMsg = String.format("%s -> %s", statusCode, body);
+            throw new Exception(errMsg);
+        }
+
+        Type type = new TypeToken<WebResult<UploadFileRet>>(){}.getType();
+        WebResult<UploadFileRet> webResult = JsonConverter.jsonToObject(body, type);
+        if (webResult.getCode() != 0) {
+            String errMsg = String.format("%s - %s", webResult.getCode(), webResult.getMsg());
+            throw new Exception(errMsg);
+        }
+
+        return webResult.getData();
+    }
+}

+ 43 - 0
oss-sdk/src/test/java/ObjectTest.java

@@ -0,0 +1,43 @@
+import cn.reghao.oss.api.constant.UploadChannel;
+import cn.reghao.oss.api.rest.UploadFileRet;
+import cn.reghao.oss.sdk.ObjectMultipartUploadService;
+import cn.reghao.oss.sdk.ObjectUploadService;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.*;
+
+/**
+ * @author reghao
+ * @date 2023-06-02 16:19:04
+ */
+@Slf4j
+public class ObjectTest {
+    static final String endpoint = "";
+    static ObjectUploadService objectUploadService = new ObjectUploadService(endpoint);
+
+    static void upload(File file) throws Exception {
+        long userId = 10001;
+        UploadFileRet uploadFileRet = objectUploadService.postObject(file, UploadChannel.video.getCode(), userId);
+        //UploadFileRet uploadFileRet = objectUploadService.putObject(file, 1, userId);
+        if (uploadFileRet == null) {
+            log.info("文件上传失败");
+        } else {
+            log.info("{} -> {}", uploadFileRet.getUploadId(), uploadFileRet.getUrl());
+        }
+    }
+
+    static void multipartUpload() throws Exception {
+        ObjectMultipartUploadService multipartUploadService = new ObjectMultipartUploadService(endpoint);
+        String filePath = "";
+        int channelId = 1;
+        multipartUploadService.upload(new File(filePath), channelId);
+        //multipartUploadService.create();
+        //multipartUploadService.get();
+    }
+
+    public static void main(String[] args) throws Exception {
+        String filePath = "";
+        File file = new File(filePath);
+        objectUploadService.postObjectWithJdkHttp(file, UploadChannel.image.getCode(), 10000);
+    }
+}

+ 7 - 0
oss-store/Dockerfile

@@ -0,0 +1,7 @@
+FROM adoptopenjdk/openjdk11:x86_64-alpine-jre-11.0.15_10
+
+WORKDIR /app
+RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
+COPY target/oss-store.jar /app/oss-store.jar
+
+ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/oss-store.jar"]

+ 6 - 0
oss-store/README.md

@@ -0,0 +1,6 @@
+# oss-store
+一个文件存储系统, 提供文件的存储, 访问和删除等服务, 不支持文件修改.
+
+## 依赖的第三方服务
+- mysql
+- zookeeper

+ 17 - 0
oss-store/bin/oss.yml

@@ -0,0 +1,17 @@
+server:
+  tomcat:
+    basedir: /opt/tmp/tomcat
+dubbo:
+  registry:
+    address: zookeeper://127.0.0.1:2181
+spring:
+  datasource:
+    url: jdbc:mysql://localhost:3306/reghao_oss_rdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
+    username: dev
+    password: Dev@123456
+oss:
+  domain: oss.reghao.cn
+  diskDirs:
+    - /opt/oss/disk/13f654c8-af87-4710-aac9-7aa086c99aec/
+  referer: reghao.cn
+  secretKey: oss-store

+ 5 - 0
oss-store/bin/shutdown.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+app='oss-store.jar'
+pid=`jps | grep ${app} | awk '{print $1}'`
+kill -15 ${pid}

+ 6 - 0
oss-store/bin/start.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+
+app_dir=`pwd`
+app_name='oss-store.jar'
+# 使用 mvn clean package -Dmaven.test.skip 生成的 jar 运行时加载 application.yml 和 oss.yml 两个配置文件, 分别位于 classpath 和文件系统路径
+java -jar ${app_dir}"/"${app_name} --spring.config.location=classpath:/application.yml,file:${app_dir}/oss.yml > console.log 2>&1 &

+ 190 - 0
oss-store/pom.xml

@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>cn.reghao.oss</groupId>
+    <artifactId>oss-store</artifactId>
+    <version>1.0.0</version>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>2.3.9.RELEASE</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.reghao.jutil</groupId>
+            <artifactId>jdk</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.reghao.jutil</groupId>
+            <artifactId>tool</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.reghao.jutil</groupId>
+            <artifactId>web</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.reghao.jutil</groupId>
+            <artifactId>media</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.reghao.oss</groupId>
+            <artifactId>oss-api</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.6</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+            <version>1.2.3</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.2.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+            <version>1.3.2</version>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.17</version>
+        </dependency>
+        <dependency>
+            <groupId>com.zaxxer</groupId>
+            <artifactId>HikariCP</artifactId>
+            <version>3.3.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-spring-boot-starter</artifactId>
+            <version>2.7.8</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-recipes</artifactId>
+            <version>2.12.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.tika</groupId>
+            <artifactId>tika-core</artifactId>
+            <version>2.9.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.xml.bind</groupId>
+            <artifactId>jaxb-api</artifactId>
+            <version>2.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.xml.bind</groupId>
+            <artifactId>jaxb-impl</artifactId>
+            <version>2.3.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.xml.bind</groupId>
+            <artifactId>jaxb-core</artifactId>
+            <version>2.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.activation</groupId>
+            <artifactId>activation</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>oss-store</finalName>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>application.yml</include>
+                    <include>mapper/**</include>
+                    <include>*.xml</include>
+                </includes>
+            </resource>
+        </resources>
+
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.3.9.RELEASE</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 11 - 0
oss-store/src/main/java/cn/reghao/oss/store/OssStoreApplication.java

@@ -0,0 +1,11 @@
+package cn.reghao.oss.store;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class OssStoreApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(OssStoreApplication.class, args);
+    }
+}

+ 40 - 0
oss-store/src/main/java/cn/reghao/oss/store/config/CacheConfig.java

@@ -0,0 +1,40 @@
+package cn.reghao.oss.store.config;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+
+import org.springframework.cache.caffeine.CaffeineCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 缓存配置
+ *
+ * @author reghao
+ * @date 2023-05-22 16:31:04
+ */
+@EnableCaching
+@Configuration
+public class CacheConfig {
+    @Bean
+    public CacheManager cacheManager() {
+        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
+        Caffeine<Object, Object> caffeineCache = Caffeine.newBuilder()
+                .initialCapacity(1000)
+                .maximumSize(10_000)
+                .expireAfterAccess(365, TimeUnit.DAYS);;
+        cacheManager.setCaffeine(caffeineCache);
+        return cacheManager;
+    }
+
+    /*@Bean("caffeineCache")
+    public Caffeine<Object, Object> caffeineCache() {
+        return Caffeine.newBuilder()
+                .initialCapacity(1000)
+                .maximumSize(10_000)
+                .expireAfterAccess(365, TimeUnit.DAYS);
+    }*/
+}

+ 23 - 0
oss-store/src/main/java/cn/reghao/oss/store/config/OssProperties.java

@@ -0,0 +1,23 @@
+package cn.reghao.oss.store.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2021-12-30 11:01:46
+ */
+@Getter
+@Setter
+@Component
+@ConfigurationProperties(prefix = "oss")
+public class OssProperties {
+    private String domain;
+    private List<String> diskDirs;
+    private String secretKey;
+    private String referer;
+}

+ 68 - 0
oss-store/src/main/java/cn/reghao/oss/store/config/mysql/DataSourceConfig.java

@@ -0,0 +1,68 @@
+package cn.reghao.oss.store.config.mysql;
+
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+
+import javax.sql.DataSource;
+
+/**
+ * MyBatis 初始化配置
+ * 若配置了多数据源,则不启用此配置
+ *
+ * @author reghao
+ * @date 2021-04-26 17:48:29
+ */
+@Configuration
+public class DataSourceConfig {
+    private final DataSource dataSource;
+
+    public DataSourceConfig(DataSource dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    @Bean
+    public SqlSessionFactory sqlSessionFactory() throws Exception {
+        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
+        factoryBean.setDataSource(dataSource);
+        factoryBean.setPlugins(new Interceptor[]{pageListInterceptor()});
+
+        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
+        configuration.setMapUnderscoreToCamelCase(true);
+        configuration.setDefaultFetchSize(100);
+        configuration.setDefaultStatementTimeout(300);
+        factoryBean.setConfiguration(configuration);
+
+        String location = "classpath*:mapper/**.xml";
+        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(location));
+        return factoryBean.getObject();
+    }
+
+    /**
+     * 配置 mybatis 的分页拦截器
+     *
+     * @param
+     * @return
+     * @date 2021-12-21 下午5:23
+     */
+    @Bean
+    public PageListInterceptor pageListInterceptor() {
+        return new PageListInterceptor();
+    }
+
+    @Bean(value = "sqlSession")
+    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
+        return new SqlSessionTemplate(sqlSessionFactory);
+    }
+
+    @Bean(value = "transactionManager")
+    public PlatformTransactionManager annotationDrivenTransactionManager() {
+        return new DataSourceTransactionManager(dataSource);
+    }
+}

+ 83 - 0
oss-store/src/main/java/cn/reghao/oss/store/config/mysql/PageListInterceptor.java

@@ -0,0 +1,83 @@
+package cn.reghao.oss.store.config.mysql;
+
+import cn.reghao.jutil.jdk.db.Page;
+import org.apache.ibatis.binding.MapperMethod;
+import org.apache.ibatis.executor.parameter.ParameterHandler;
+import org.apache.ibatis.executor.statement.StatementHandler;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.plugin.*;
+import org.apache.ibatis.reflection.MetaObject;
+import org.apache.ibatis.reflection.SystemMetaObject;
+
+import java.sql.Connection;
+import java.util.Properties;
+
+/**
+ * MyBatis 分页拦截器
+ *
+ * @author reghao
+ * @date 2021-04-26 17:40:32
+ */
+@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class,Integer.class})})
+public class PageListInterceptor implements Interceptor {
+    private final String methodSuffix = "ByPage";
+    @Deprecated
+    private int page;
+    @Deprecated
+    private int size;
+    private String dbType;
+
+    @Override
+    public Object intercept(Invocation invocation) throws Throwable {
+        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
+        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
+        while(metaObject.hasGetter("h")){
+            Object object = metaObject.getValue("h");
+            metaObject = SystemMetaObject.forObject(object);
+        }
+
+        while(metaObject.hasGetter("target")){
+            Object object = metaObject.getValue("target");
+            metaObject = SystemMetaObject.forObject(object);
+        }
+
+        MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
+        String mapId = mappedStatement.getId();
+        if (mapId.matches(String.format(".+%s$", methodSuffix))){
+            ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler");
+            Object object = parameterHandler.getParameterObject();
+            Page page;
+            if (object instanceof Page) {
+                page = (Page) object;
+            } else if (object instanceof MapperMethod.ParamMap) {
+                MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) object;
+                Object param1 = paramMap.get("param1");
+                if (param1 instanceof Page) {
+                    page = (Page) param1;
+                } else {
+                    throw new Exception("byPage 方法的第一个参数不是 Page 类型");
+                }
+            } else {
+                throw new Exception("没有 Page 类型的参数");
+            }
+
+            String sql = (String) metaObject.getValue("delegate.boundSql.sql");
+            //sql += " limit "+(page-1)*size +","+size;
+            sql += String.format(" limit %s,%s", (page.getPage()-1)*page.getSize(), page.getSize());
+            metaObject.setValue("delegate.boundSql.sql", sql);
+        }
+        return invocation.proceed();
+    }
+
+    @Override
+    public Object plugin(Object target) {
+        return Plugin.wrap(target, this);
+    }
+
+    @Override
+    public void setProperties(Properties properties) {
+        String limit = properties.getProperty("limit","10");
+        this.page = Integer.parseInt(limit);
+        this.dbType = properties.getProperty("dbType", "mysql");
+    }
+}

+ 77 - 0
oss-store/src/main/java/cn/reghao/oss/store/config/spring/FileLifecycle.java

@@ -0,0 +1,77 @@
+package cn.reghao.oss.store.config.spring;
+
+import cn.reghao.jutil.jdk.store.LocalStores;
+import cn.reghao.jutil.jdk.store.SubDirCount;
+import cn.reghao.oss.store.config.OssProperties;
+import cn.reghao.oss.store.db.mapper.DataBlockMapper;
+import cn.reghao.oss.store.task.FileTask;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2022-03-23 09:22:01
+ */
+@Slf4j
+@Component
+public class FileLifecycle implements ApplicationRunner, DisposableBean {
+    private final OssProperties ossProperties;
+    private final DataBlockMapper dataBlockMapper;
+    private final FileTask fileTask;
+
+    public FileLifecycle(OssProperties ossProperties, DataBlockMapper dataBlockMapper, FileTask fileTask) {
+        this.ossProperties = ossProperties;
+        this.dataBlockMapper = dataBlockMapper;
+        this.fileTask = fileTask;
+    }
+
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        initLocalStore();
+
+        log.info("执行文件任务...");
+        fileTask.exec();
+        log.info("文件任务执行完成...");
+    }
+
+    private void initLocalStore() throws IOException {
+        log.info("初始化本地磁盘...");
+        List<String> diskDirs = ossProperties.getDiskDirs();
+        for (String diskDir : diskDirs) {
+            File dir = new File(diskDir);
+            if (!dir.exists()) {
+                dir.mkdirs();
+            }
+        }
+        LocalStores.init(diskDirs);
+
+        for (String diskDir : diskDirs) {
+            List<SubDirCount> list = dataBlockMapper.findSubDirCount("");
+            Map<String, Integer> map = new HashMap<>();
+            list.forEach(subDirCount -> {
+                String relativeDir = subDirCount.getRelativeDir();
+                String storeDir = diskDir + relativeDir;
+                File file = new File(storeDir);
+
+                int total = subDirCount.getTotal();
+                map.put(file.getAbsolutePath(), total);
+            });
+
+            LocalStores.initStoreDirs(diskDir, map);
+        }
+        log.info("本地磁盘数据初始化完成...");
+    }
+
+    @Override
+    public void destroy() {
+    }
+}

+ 61 - 0
oss-store/src/main/java/cn/reghao/oss/store/config/web/PutMessageConverter.java

@@ -0,0 +1,61 @@
+package cn.reghao.oss.store.config.web;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.HttpOutputMessage;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.AbstractHttpMessageConverter;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.http.converter.HttpMessageNotWritableException;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.UUID;
+
+/**
+ * 实现 PUT 请求上传文件
+ * @author reghao
+ * @date 2022-12-28 10:03:22
+ */
+@Slf4j
+public class PutMessageConverter extends AbstractHttpMessageConverter<File> {
+    public PutMessageConverter() {
+        super(MediaType.ALL);
+    }
+
+    @Override
+    protected boolean supports(Class<?> clazz) {
+        return File.class.isAssignableFrom(clazz);
+    }
+
+    @Override
+    protected File readInternal(Class<? extends File> clazz, HttpInputMessage inputMessage)
+            throws IOException, HttpMessageNotReadableException {
+        log.info("PUT 请求上传文件");
+        InputStream inputStream = inputMessage.getBody();
+        return saveStream(inputStream);
+    }
+
+    private File saveStream(InputStream inputStream) throws IOException {
+        String tmpPath = String.format("/opt/tmp/tomcat/%s.dat", UUID.randomUUID());
+        Files.copy(inputStream, Path.of(tmpPath), StandardCopyOption.REPLACE_EXISTING);
+        File tmpFile = new File(tmpPath);
+        /*tmpFile.createNewFile();
+        FileOutputStream outputStream = new FileOutputStream(tmpFile);
+        byte[] buffer = new byte[1024];
+        int bytesRead;
+        while ((bytesRead = inputStream.read(buffer)) > 0) {
+            outputStream.write(buffer, 0, bytesRead);
+        }
+        outputStream.flush();
+        outputStream.close();*/
+        return tmpFile;
+    }
+
+    protected void writeInternal(File file, HttpOutputMessage outputMessage)
+            throws IOException, HttpMessageNotWritableException {
+        log.info("writeInternal");
+    }
+}

+ 72 - 0
oss-store/src/main/java/cn/reghao/oss/store/config/web/WebConfig.java

@@ -0,0 +1,72 @@
+package cn.reghao.oss.store.config.web;
+
+import cn.reghao.oss.store.inerceptor.AccessLogInterceptor;
+import cn.reghao.oss.store.inerceptor.TokenFilter;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
+
+import javax.servlet.Filter;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2021-12-30 12:34:26
+ */
+@Configuration
+public class WebConfig extends WebMvcConfigurationSupport {
+    private final AccessLogInterceptor accessLogInterceptor;
+
+    public WebConfig(AccessLogInterceptor accessLogInterceptor) {
+        this.accessLogInterceptor = accessLogInterceptor;
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(accessLogInterceptor);
+    }
+
+    // 跨域处理
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedOrigins("*")
+                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
+                .allowCredentials(true)
+                .maxAge(3600)
+                .allowedHeaders("*");
+    }
+
+    @Bean
+    public FilterRegistrationBean<Filter> filterRegistrationBean(TokenFilter tokenFilter) {
+        FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
+        registrationBean.setFilter(tokenFilter);
+        registrationBean.addUrlPatterns("*");
+        return registrationBean;
+    }
+
+    /**
+     * HTTP req/resp 消息转换器
+     *
+     * @param
+     * @return
+     * @date 2022-12-29 上午9:43
+     */
+    @Override
+    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+        // 处理 application/octet-stream 数据
+        converters.add(new PutMessageConverter());
+        // 处理 application/json 数据
+        converters.add(new StringHttpMessageConverter());
+        converters.add(new MappingJackson2HttpMessageConverter());
+        // 处理 application/xml 数据
+        converters.add(new Jaxb2RootElementHttpMessageConverter());
+    }
+}

+ 106 - 0
oss-store/src/main/java/cn/reghao/oss/store/controller/ObjectGetController.java

@@ -0,0 +1,106 @@
+package cn.reghao.oss.store.controller;
+
+import cn.reghao.oss.api.constant.ChannelAction;
+import cn.reghao.oss.store.config.OssProperties;
+import cn.reghao.oss.store.service.GetObjectService;
+import cn.reghao.oss.store.util.JwtUtil;
+import cn.reghao.oss.store.util.ObjectUtil;
+import cn.reghao.oss.store.util.SignatureUtil;
+import cn.reghao.oss.api.constant.UploadChannel;
+import cn.reghao.oss.api.dto.OssPayload;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author reghao
+ * @date 2022-12-08 20:38:33
+ */
+@RestController
+public class ObjectGetController {
+    private final GetObjectService getObjectService;
+    private final Cache<String, String> cache;
+    private final OssProperties ossProperties;
+
+    public ObjectGetController(GetObjectService getObjectService, OssProperties ossProperties) {
+        this.getObjectService = getObjectService;
+        this.cache = Caffeine.newBuilder().maximumSize(10_000).expireAfterAccess(1, TimeUnit.HOURS).build();
+        this.ossProperties = ossProperties;
+    }
+
+    @RequestMapping(value = "/**", method = RequestMethod.HEAD)
+    public void headObject(@RequestParam(value = "sha256sum", required = false) String sha256sum) throws IOException {
+        if (sha256sum != null) {
+            getObjectService.headObject1(sha256sum);
+        } else {
+            String objectName = ObjectUtil.getObjectName();
+            getObjectService.headObject(objectName);
+        }
+    }
+
+    @GetMapping(value = "/**")
+    public void getObject(@RequestParam(value = "token", required = false) String token,
+                          @RequestParam(value = "t", required = false) Long timestamp,
+                          @RequestParam(value = "nonce", required = false) String nonce,
+                          @RequestParam(value = "sign", required = false) String sign,
+                          @RequestParam(value = "client", required = false) String client) throws IOException {
+        String objectName = ObjectUtil.getObjectName();
+        if (client != null && !client.isBlank()) {
+            getObjectService.getObject(objectName);
+            return;
+        }
+
+        if (objectName.startsWith(UploadChannel.avatar.getPrefix())
+                || objectName.startsWith(UploadChannel.image.getPrefix())
+                || objectName.startsWith(UploadChannel.img.getPrefix())) {
+            getObjectService.getObject(objectName);
+            return;
+        }
+
+        String queryString = String.format("token=%s&t=%s&nonce=%s", token, timestamp, nonce);
+        String domain = ossProperties.getDomain();
+        String url = String.format("//%s/%s", domain, objectName);
+        String requestString = String.format("%s%s?%s", "GET", url, queryString);
+        boolean valid = SignatureUtil.valid(requestString, ossProperties.getSecretKey(), sign);
+        if (!valid) {
+            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+
+        long current = System.currentTimeMillis();
+        if (current > timestamp) {
+            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+
+        /*String value = cache.getIfPresent(nonce);
+        if (value == null) {
+            cache.put(nonce, nonce);
+        } else {
+            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }*/
+
+        OssPayload ossPayload = JwtUtil.getOssPayload(token, ossProperties.getSecretKey());
+        int channelId = ossPayload.getChannelId();
+        long userId = ossPayload.getUserId();
+        String prefix = UploadChannel.getUploadChannel(channelId).getPrefix();
+        if (!objectName.startsWith(prefix)) {
+            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+
+        String action = ossPayload.getAction();
+        if (ChannelAction.access.getName().equals(action)) {
+            getObjectService.getObject(objectName);
+        } else if (ChannelAction.download.getName().equals(action)) {
+            getObjectService.downloadObject(objectName);
+        } else {
+            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN);
+        }
+    }
+}

+ 51 - 0
oss-store/src/main/java/cn/reghao/oss/store/controller/ObjectMultipartUploadController.java

@@ -0,0 +1,51 @@
+package cn.reghao.oss.store.controller;
+
+import cn.reghao.oss.store.service.ObjectMultipartUploadService;
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.oss.api.rest.*;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.*;
+
+/**
+ * @author reghao
+ * @date 2022-12-08 20:40:55
+ */
+@RestController
+public class ObjectMultipartUploadController {
+    private final ObjectMultipartUploadService objectMultipartUploadService;
+
+    public ObjectMultipartUploadController(ObjectMultipartUploadService objectMultipartUploadService) {
+        this.objectMultipartUploadService = objectMultipartUploadService;
+    }
+
+    @PostMapping(value = "/", params = {"create"}, produces = MediaType.APPLICATION_JSON_VALUE)
+    public String create(@Validated UploadPrepare uploadPrepare) {
+        UploadPrepareRet uploadPrepareRet = objectMultipartUploadService.prepareUpload(uploadPrepare);
+        return WebResult.success(uploadPrepareRet);
+    }
+
+    @GetMapping(value = "/", params = {"multipart"}, produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getUploadedPart() {
+        UploadedPart uploadedPart = new UploadedPart();
+        uploadedPart.setSkipUpload(false);
+        uploadedPart.setNeedMerge(false);
+        uploadedPart.setUrl("");
+        uploadedPart.setUploaded(Collections.emptyList());
+        return WebResult.success(uploadedPart);
+    }
+
+    @PostMapping(value = "/", params = {"multipart"}, produces = MediaType.APPLICATION_JSON_VALUE)
+    public String uploadPart(MultipartFile file, @Validated UploadFilePart uploadFilePart) throws Exception {
+        UploadFileRet uploadFileRet = objectMultipartUploadService.putFilePart(file.getInputStream(), uploadFilePart);
+        return WebResult.success(uploadFileRet);
+    }
+
+    @PostMapping(value = "/", params = {"merge"}, produces = MediaType.APPLICATION_JSON_VALUE)
+    public String merge() {
+        return WebResult.success();
+    }
+}

+ 130 - 0
oss-store/src/main/java/cn/reghao/oss/store/controller/ObjectUploadController.java

@@ -0,0 +1,130 @@
+package cn.reghao.oss.store.controller;
+
+import cn.reghao.oss.store.config.OssProperties;
+import cn.reghao.oss.store.model.vo.ObjectProp;
+import cn.reghao.oss.store.model.vo.ObjectResult;
+import cn.reghao.oss.store.service.ChannelValidateService;
+import cn.reghao.oss.store.service.FileStoreService;
+import cn.reghao.oss.store.service.ObjectNameService;
+import cn.reghao.oss.store.service.PutObjectService;
+import cn.reghao.oss.store.task.FileProcessor;
+import cn.reghao.oss.store.util.JwtUtil;
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.jutil.jdk.security.DigestUtil;
+import cn.reghao.jutil.web.ServletUtil;
+import cn.reghao.oss.api.dto.OssPayload;
+import cn.reghao.oss.api.rest.UploadFileRet;
+import cn.reghao.oss.store.util.UserContext;
+import org.apache.commons.io.FileUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.util.UUID;
+
+/**
+ * @author reghao
+ * @date 2023-05-19 16:20:12
+ */
+@RestController
+public class ObjectUploadController {
+    private final ChannelValidateService channelValidateService;
+    private final FileStoreService fileStoreService;
+    private final ObjectNameService objectNameService;
+    private final PutObjectService putObjectService;
+    private final FileProcessor fileProcessor;
+    private final OssProperties ossProperties;
+
+    public ObjectUploadController(ChannelValidateService channelValidateService, FileStoreService fileStoreService,
+                                  ObjectNameService objectNameService, PutObjectService putObjectService,
+                                  FileProcessor fileProcessor, OssProperties ossProperties) {
+        this.channelValidateService = channelValidateService;
+        this.fileStoreService = fileStoreService;
+        this.objectNameService = objectNameService;
+        this.putObjectService = putObjectService;
+        this.fileProcessor = fileProcessor;
+        this.ossProperties = ossProperties;
+    }
+
+    @PutMapping(value = "/**")
+    public String putObject(@RequestBody File file) {
+        return WebResult.failWithMsg("not implement");
+    }
+
+    @PostMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<String> postObject(MultipartFile file, Integer channelId,
+                                             String client, String sha256sum, Long userId) throws Exception {
+        /* permission check */
+        if (client == null) {
+            String token = ServletUtil.getBearerToken();
+            if (token == null) {
+                return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
+                        .body(WebResult.failWithMsg("no token in request"));
+            }
+
+            OssPayload ossPayload = JwtUtil.getOssPayload(token, ossProperties.getSecretKey());
+            String action = ossPayload.getAction();
+            if (!"upload".equals(action)) {
+                return ResponseEntity.status(HttpStatus.FORBIDDEN)
+                        .body(WebResult.failWithMsg("it's not upload token"));
+            }
+
+            int channelId1 = ossPayload.getChannelId();
+            if (channelId != channelId1) {
+                return ResponseEntity.status(HttpStatus.FORBIDDEN)
+                        .body(WebResult.failWithMsg("channel not match in token"));
+            }
+
+            long userId1 = ossPayload.getUserId();
+            UserContext context = new UserContext(userId1);
+        } else {
+            UserContext context = new UserContext(userId);
+        }
+
+        /* channel validate */
+        String contentId = UUID.randomUUID().toString().replace("-", "");
+        long size = file.getSize();
+        File savedFile = fileStoreService.saveFile(file.getInputStream(), contentId, size);
+        Result result = channelValidateService.validate(savedFile, channelId);
+        if (result.getCode() != 0) {
+            FileUtils.deleteQuietly(savedFile);
+            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(WebResult.result(result));
+        }
+
+        /* store file */
+        String sha256sum1 = sha256sum;
+        if (sha256sum1 == null) {
+            sha256sum1 = DigestUtil.sha256sum(savedFile.getAbsolutePath());
+        }
+
+        String filename = file.getOriginalFilename();
+        if (filename == null) {
+            filename = "";
+        }
+
+        ObjectProp objectProp = objectNameService.getObjectProp(channelId, filename);
+        ObjectResult objectResult = putObjectService.putObject(objectProp, contentId, savedFile, filename, sha256sum1);
+        UploadFileRet uploadFileRet;
+        String errMsg;
+        try {
+            uploadFileRet = fileProcessor.process(objectResult, channelId);
+            return ResponseEntity.status(HttpStatus.OK).body(WebResult.success(uploadFileRet));
+        } catch (Exception e) {
+            e.printStackTrace();
+            errMsg = e.getMessage();
+        }
+
+        putObjectService.deleteObject(objectResult.getObjectId());
+        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errMsg);
+    }
+
+    @DeleteMapping(value = "/")
+    public ResponseEntity<String> deleteObject(String objectId) {
+        //putObjectService.deleteObject(objectId);
+        return ResponseEntity.ok().build();
+    }
+}

+ 20 - 0
oss-store/src/main/java/cn/reghao/oss/store/db/mapper/AudioFileMapper.java

@@ -0,0 +1,20 @@
+package cn.reghao.oss.store.db.mapper;
+
+import cn.reghao.oss.store.model.po.AudioFile;
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.oss.api.dto.media.AudioInfo;
+import cn.reghao.oss.api.dto.media.AudioUrl;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-07-05 16:47:46
+ */
+@Mapper
+public interface AudioFileMapper extends BaseMapper<AudioFile> {
+    List<AudioFile> findByAudioFileId(String audioFileId);
+    List<AudioInfo> findAudioInfo(String audioFileId);
+    List<AudioUrl> findAudioUrl(String audioFileId);
+}

+ 23 - 0
oss-store/src/main/java/cn/reghao/oss/store/db/mapper/DataBlockMapper.java

@@ -0,0 +1,23 @@
+package cn.reghao.oss.store.db.mapper;
+
+import cn.reghao.jutil.jdk.store.SubDirCount;
+import cn.reghao.oss.store.model.po.DataBlock;
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2022-11-24 11:11:24
+ */
+@Mapper
+public interface DataBlockMapper extends BaseMapper<DataBlock> {
+    void updateParent(@Param("id") int id, @Param("relativeDir") String relativeDir);
+    void updateBatch(List<DataBlock> list);
+
+    List<DataBlock> findDataBlocks(@Param("pageSize") int pageSize, @Param("nextId") int nextId);
+    DataBlock findByContentId(String contentId);
+    List<SubDirCount> findSubDirCount(String baseDir);
+}

+ 56 - 0
oss-store/src/main/java/cn/reghao/oss/store/db/mapper/FileMetaMapper.java

@@ -0,0 +1,56 @@
+package cn.reghao.oss.store.db.mapper;
+
+import cn.reghao.oss.api.dto.DeleteFile;
+import cn.reghao.oss.api.dto.FileInfo;
+import cn.reghao.oss.store.model.po.FileMeta;
+import cn.reghao.oss.api.dto.ObjectMeta;
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.jutil.jdk.db.Page;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2022-11-24 11:11:06
+ */
+@Mapper
+public interface FileMetaMapper extends BaseMapper<FileMeta> {
+    @Deprecated
+    void updateScopeByObjectIds(@Param("scope") int scope, @Param("list") List<String> list);
+    void updateScopeByObjectNames(@Param("scope") int scope, @Param("list") List<String> list);
+
+    List<FileMeta> findFileMetaByPage(Page page);
+    FileMeta findBySha256sum(String sha256sum);
+    FileMeta findByObjectName(String objectName);
+    List<FileMeta> findByObjectNames(List<String> list);
+    FileMeta findByObjectId(String objectId);
+    List<FileMeta> findByContentId(String contentId);
+    ObjectMeta findObjectMeta(String objectName);
+    ObjectMeta findObjectMetaById(String objectName);
+    List<String> findObjectNames(List<String> list);
+
+    /******************************************************************************************************************/
+    List<FileMeta> findAll0(@Param("bucket") String bucket, @Param("max") Integer max, @Param("regex") String regex);
+    List<FileMeta> findAll1(@Param("bucket") String bucket, @Param("prefix") String prefix, @Param("max") Integer max);
+    List<FileMeta> findAll2(@Param("bucket") String bucket, @Param("prefix") String prefix, @Param("start") String start, @Param("max") Integer max);
+    void updateFilename(@Param("objectId") String objectId, @Param("filename") String filename);
+    void updateParent(@Param("objectId") String objectId, @Param("pid") String pid);
+    void updateBatch(List<FileMeta> list);
+
+    int countByPid(String pid);
+    List<FileInfo> findFileInfoByPage(Page page, @Param("pid") String pid);
+    List<FileInfo> findFileInfo1ByPage(Page page, @Param("pid") String pid);
+    List<FileInfo> findDirectories(String pid);
+
+    int countByKeyword(String keyword);
+    List<FileInfo> findKeywordByPage(Page page, @Param("keyword") String keyword);
+
+    int countDeletedFiles();
+    List<DeleteFile> findDeletedFileByPage(Page page);
+    List<FileMeta> findFileInfos(@Param("objectIds") List<String> objectIds);
+    List<FileMeta> findByFilename(@Param("pid") String pid, @Param("filename") String filename);
+    List<FileMeta> findByPid(String pid);
+    List<String> findObjectPrefix();
+}

+ 13 - 0
oss-store/src/main/java/cn/reghao/oss/store/db/mapper/FileTypeMapper.java

@@ -0,0 +1,13 @@
+package cn.reghao.oss.store.db.mapper;
+
+import cn.reghao.oss.store.model.po.FileType;
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author reghao
+ * @date 2022-12-27 16:59:15
+ */
+@Mapper
+public interface FileTypeMapper extends BaseMapper<FileType> {
+}

+ 23 - 0
oss-store/src/main/java/cn/reghao/oss/store/db/mapper/ImageFileMapper.java

@@ -0,0 +1,23 @@
+package cn.reghao.oss.store.db.mapper;
+
+import cn.reghao.oss.store.model.po.ImageFile;
+import cn.reghao.oss.store.model.vo.ImageObject;
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author reghao
+ * @date 2021-12-08 14:41:35
+ */
+@Mapper
+public interface ImageFileMapper extends BaseMapper<ImageFile> {
+    void deleteByImageFileId(String imageFileId);
+    void deleteByImageFileIds(List<String> list);
+
+    List<ImageFile> findByImageFileId(String imageFileId);
+    List<ImageFile> findByImageFileIds(Set<String> list);
+    List<ImageObject> findAll1();
+}

+ 22 - 0
oss-store/src/main/java/cn/reghao/oss/store/db/mapper/VideoFileMapper.java

@@ -0,0 +1,22 @@
+package cn.reghao.oss.store.db.mapper;
+
+import cn.reghao.oss.store.model.po.VideoFile;
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.oss.api.dto.media.VideoInfo;
+import cn.reghao.oss.api.dto.media.VideoUrlDto;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-10-13 15:59:11
+ */
+@Mapper
+public interface VideoFileMapper extends BaseMapper<VideoFile> {
+    void deleteByVideoFileId(String videoFileId);
+
+    List<VideoUrlDto> findVideoUrls(String videoFileId);
+    List<VideoFile> findByVideoFileId(String videoFileId);
+    List<VideoInfo> findVideoInfo(String videoFileId);
+}

+ 40 - 0
oss-store/src/main/java/cn/reghao/oss/store/db/repository/AudioRepository.java

@@ -0,0 +1,40 @@
+package cn.reghao.oss.store.db.repository;
+
+import cn.reghao.oss.store.db.mapper.*;
+import cn.reghao.oss.store.model.po.*;
+import cn.reghao.oss.api.dto.media.AudioInfo;
+import cn.reghao.oss.api.dto.media.AudioUrl;
+import cn.reghao.oss.store.db.mapper.AudioFileMapper;
+import cn.reghao.oss.store.model.po.AudioFile;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-08-28 15:00:43
+ */
+@Repository
+public class AudioRepository {
+    private final AudioFileMapper audioFileMapper;
+
+    public AudioRepository(AudioFileMapper audioFileMapper) {
+        this.audioFileMapper = audioFileMapper;
+    }
+
+    public void saveAudioFiles(List<AudioFile> audioFiles) {
+        audioFileMapper.saveAll(audioFiles);
+    }
+
+    public List<AudioFile> findAudioFiles(String audioFileId) {
+        return audioFileMapper.findByAudioFileId(audioFileId);
+    }
+
+    public List<AudioInfo> getAudioInfo(String audioFileId) {
+        return audioFileMapper.findAudioInfo(audioFileId);
+    }
+
+    public List<AudioUrl> getAudioUrls(String audioFileId) {
+        return audioFileMapper.findAudioUrl(audioFileId);
+    }
+}

+ 96 - 0
oss-store/src/main/java/cn/reghao/oss/store/db/repository/ImageRepository.java

@@ -0,0 +1,96 @@
+package cn.reghao.oss.store.db.repository;
+
+import cn.reghao.oss.api.constant.UploadChannel;
+import cn.reghao.oss.api.dto.media.ImageUrlDto;
+import cn.reghao.oss.store.db.mapper.ImageFileMapper;
+import cn.reghao.oss.store.model.po.FileMeta;
+import cn.reghao.oss.store.model.po.ImageFile;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2023-08-28 15:00:43
+ */
+@Repository
+public class ImageRepository {
+    private final ImageFileMapper imageFileMapper;
+    private final ObjectRepository objectRepository;
+
+    public ImageRepository(ImageFileMapper imageFileMapper, ObjectRepository objectRepository) {
+        this.imageFileMapper = imageFileMapper;
+        this.objectRepository = objectRepository;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void saveImageFiles(List<ImageFile> imageFiles) {
+        imageFileMapper.saveAll(imageFiles);
+    }
+
+    public void deleteByImageFileIds(List<String> imageFileIds) {
+        imageFileIds.forEach(this::deleteImageFile);
+    }
+
+    public void deleteImageFile(String imageFileId) {
+        List<String> objectIds = imageFileMapper.findByImageFileId(imageFileId).stream()
+                .map(ImageFile::getObjectId)
+                .collect(Collectors.toList());
+        deleteImageFile0(imageFileId, objectIds);
+    }
+
+    public void deleteByObjectNames(List<String> imageObjectNames) {
+        List<FileMeta> fileMetas = objectRepository.getByObjectNames(imageObjectNames);
+        fileMetas.forEach(fileMeta -> {
+            String objectId = fileMeta.getObjectId();
+            deleteImageFile(objectId);
+        });
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteImageFile0(String imageFileId, List<String> objectIds) {
+        imageFileMapper.deleteByImageFileId(imageFileId);
+        objectRepository.deleteByObjectIds(objectIds);
+    }
+
+    public List<ImageFile> findImageFiles(String imageFileId) {
+        return imageFileMapper.findByImageFileId(imageFileId);
+    }
+
+    public List<ImageUrlDto> getImageUrls(int channelId, Set<String> imageFileIds) {
+        List<ImageUrlDto> list = new ArrayList<>();
+        for (String imageFileId : imageFileIds) {
+            ImageUrlDto imageUrlDto = getImageUrl(channelId, imageFileId);
+            if (imageUrlDto != null) {
+                list.add(imageUrlDto);
+            }
+        }
+
+        return list;
+    }
+
+    public ImageUrlDto getImageUrl(int channelId, String imageFileId) {
+        List<ImageFile> list = imageFileMapper.findByImageFileId(imageFileId);
+        if (list.isEmpty()) {
+            return null;
+        }
+
+        ImageUrlDto imageUrlDto = new ImageUrlDto(imageFileId);
+        if (channelId == UploadChannel.photo.getCode() || channelId == UploadChannel.disk.getCode()) {
+            ImageFile original = list.get(0);
+            imageUrlDto.setOriginalUrl(original.getUrl());
+            ImageFile thumbnail = list.get(1);
+            imageUrlDto.setThumbnailUrl(thumbnail.getUrl());
+        } else {
+            ImageFile original = list.get(0);
+            imageUrlDto.setOriginalUrl(original.getUrl());
+            imageUrlDto.setThumbnailUrl(original.getUrl());
+        }
+
+        return imageUrlDto;
+    }
+}

+ 115 - 0
oss-store/src/main/java/cn/reghao/oss/store/db/repository/ObjectRepository.java

@@ -0,0 +1,115 @@
+package cn.reghao.oss.store.db.repository;
+
+import cn.reghao.oss.store.db.mapper.DataBlockMapper;
+import cn.reghao.oss.store.db.mapper.FileMetaMapper;
+import cn.reghao.oss.store.model.po.DataBlock;
+import cn.reghao.oss.store.model.po.FileMeta;
+import cn.reghao.oss.api.dto.ObjectMeta;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-05-22 16:48:40
+ */
+@Slf4j
+@Service
+public class ObjectRepository {
+    private final FileMetaMapper fileMetaMapper;
+    private final DataBlockMapper dataBlockMapper;
+
+    public ObjectRepository(FileMetaMapper fileMetaMapper, DataBlockMapper dataBlockMapper) {
+        this.fileMetaMapper = fileMetaMapper;
+        this.dataBlockMapper = dataBlockMapper;
+    }
+
+    public void saveFileMeta(FileMeta fileMeta) {
+        fileMetaMapper.save(fileMeta);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void saveObject(FileMeta fileMeta, List<DataBlock> list) {
+        fileMetaMapper.save(fileMeta);
+        dataBlockMapper.saveAll(list);
+    }
+
+    //@CacheEvict(cacheNames = "oss:store:objectMeta", key = "#fileMeta.objectName")
+    @Deprecated
+    public void updateObjectScope(int scope, List<String> objectIds) {
+        fileMetaMapper.updateScopeByObjectIds(scope, objectIds);
+    }
+
+    //@CacheEvict(cacheNames = "oss:store:objectMeta", key = "#objectName")
+    public void updateObjectScope1(int scope, List<String> objectNames) {
+        fileMetaMapper.updateScopeByObjectNames(scope, objectNames);
+    }
+
+    @CacheEvict(cacheNames = "oss:store:objectMeta", key = "#fileMeta.objectName")
+    public void deleteObject(FileMeta fileMeta) {
+        String contentId = fileMeta.getContentId();
+        List<FileMeta> list = fileMetaMapper.findByContentId(contentId);
+        if (list.size() == 1) {
+            DataBlock dataBlock = dataBlockMapper.findByContentId(contentId);
+            String absolutePath = dataBlock.getAbsolutePath();
+
+            deleteObject(fileMeta, dataBlock);
+            FileUtils.deleteQuietly(new File(absolutePath));
+        } else {
+            fileMetaMapper.delete(fileMeta);
+        }
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteObject(FileMeta fileMeta, DataBlock dataBlock) {
+        fileMetaMapper.delete(fileMeta);
+        dataBlockMapper.delete(dataBlock);
+    }
+
+    private void deleteByObjectId(String objectId) {
+        FileMeta fileMeta = fileMetaMapper.findByObjectId(objectId);
+        deleteObject(fileMeta);
+    }
+
+    //@CacheEvict
+    public void deleteByObjectIds(List<String> objectIds) {
+        objectIds.forEach(this::deleteByObjectId);
+    }
+
+    public FileMeta getByObjectName(String objectName) {
+        return fileMetaMapper.findByObjectName(objectName);
+    }
+
+    public List<FileMeta> getByObjectNames(List<String> objectNames) {
+        return fileMetaMapper.findByObjectNames(objectNames);
+    }
+
+    public FileMeta getByObjectId(String objectId) {
+        return fileMetaMapper.findByObjectId(objectId);
+    }
+
+    public FileMeta getBySha256sum(String sha256sum) {
+        return fileMetaMapper.findBySha256sum(sha256sum);
+    }
+
+    @Cacheable(cacheNames = "oss:store:objectMeta", key = "#objectName", unless = "#result == null")
+    public ObjectMeta getObjectMeta(String objectName) {
+        log.info("cache miss");
+        ObjectMeta objectMeta = fileMetaMapper.findObjectMeta(objectName);
+        return objectMeta;
+    }
+
+    public ObjectMeta getObjectMetaById(String objectId) {
+        return fileMetaMapper.findObjectMetaById(objectId);
+    }
+
+    public List<String> getObjectNames(List<String> objectIds) {
+        return fileMetaMapper.findObjectNames(objectIds);
+    }
+}

+ 63 - 0
oss-store/src/main/java/cn/reghao/oss/store/db/repository/VideoRepository.java

@@ -0,0 +1,63 @@
+package cn.reghao.oss.store.db.repository;
+
+import cn.reghao.oss.store.db.mapper.*;
+import cn.reghao.oss.store.model.po.*;
+import cn.reghao.oss.api.dto.media.VideoInfo;
+import cn.reghao.oss.api.dto.media.VideoUrlDto;
+import cn.reghao.oss.store.db.mapper.VideoFileMapper;
+import cn.reghao.oss.store.model.po.VideoFile;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2023-08-28 15:00:43
+ */
+@Repository
+public class VideoRepository {
+    private final VideoFileMapper videoFileMapper;
+
+    public VideoRepository(VideoFileMapper videoFileMapper) {
+        this.videoFileMapper = videoFileMapper;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void saveVideoFiles(List<VideoFile> videoFiles) {
+        videoFileMapper.saveAll(videoFiles);
+    }
+
+    public List<String> deleteVideoFile(String videoFileId) {
+        List<String> objectIds = videoFileMapper.findByVideoFileId(videoFileId).stream()
+                .map(VideoFile::getObjectId)
+                .collect(Collectors.toList());
+        deleteVideoFile0(videoFileId);
+        return objectIds;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteVideoFile0(String videoFileId) {
+        videoFileMapper.deleteByVideoFileId(videoFileId);
+    }
+
+    public List<VideoInfo> getVideoInfos(String videoFileId) {
+        return videoFileMapper.findVideoInfo(videoFileId);
+    }
+
+    public VideoFile getOriginalVideoFile(String videoFileId) {
+        List<VideoFile> list = videoFileMapper.findByVideoFileId(videoFileId);
+        return list.isEmpty() ? null : list.get(0);
+    }
+
+    public List<VideoFile> getVideoFiles(String videoFileId) {
+        List<VideoFile> list = videoFileMapper.findByVideoFileId(videoFileId);
+        return list;
+    }
+
+    //@Cacheable(cacheNames = "oss:store:videoUrls", key = "#videoFileId", unless = "#result.empty")
+    public List<VideoUrlDto> findVideoUrls(String videoFileId) {
+        return videoFileMapper.findVideoUrls(videoFileId);
+    }
+}

+ 62 - 0
oss-store/src/main/java/cn/reghao/oss/store/exception/ControllerExceptionHandler.java

@@ -0,0 +1,62 @@
+package cn.reghao.oss.store.exception;
+
+import cn.reghao.jutil.jdk.exception.ExceptionUtil;
+import cn.reghao.jutil.jdk.result.WebResult;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.stream.Collectors;
+
+/**
+ * 全局异常处理类,处理 controller 抛出的异常
+ *
+ * @author reghao
+ * @date 2019/03/21 10:25:49
+ */
+@Slf4j
+@ControllerAdvice
+public class ControllerExceptionHandler {
+    /**
+     * 处理所有 controller 上抛出的异常
+     *
+     * @date 2019-09-28 上午11:01
+     */
+    @ExceptionHandler({Exception.class})
+    @ResponseBody
+    public ResponseEntity<String> error(Exception e, HttpServletRequest request) {
+        String uri = request.getRequestURI();
+        String msg;
+        if (e instanceof NullPointerException) {
+            msg = ExceptionUtil.stackTrace(e);
+        } else {
+            msg = ExceptionUtil.errorMsg(e);
+        }
+        log.error("{} 接口抛出异常: {}", uri, msg);
+
+        String body = WebResult.errorWithMsg(msg);
+        int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
+        int status1 = HttpStatus.INSUFFICIENT_STORAGE.value();
+        if (e instanceof MethodArgumentNotValidException) {
+            // 参数校验失败
+            MethodArgumentNotValidException exception = (MethodArgumentNotValidException) e;
+            String errMsg = exception.getBindingResult().getAllErrors().stream()
+                    .map(objectError -> {
+                        String message = objectError.getDefaultMessage();
+                        return message + "\n";
+                    }).collect(Collectors.joining());
+            body = WebResult.errorWithMsg(errMsg);
+        } else if (e instanceof IllegalStateException) {
+            IllegalStateException exception = (IllegalStateException) e;
+            Throwable throwable = exception.getCause();
+            body = WebResult.errorWithMsg(throwable.getMessage());
+        }
+
+        return ResponseEntity.status(status).body(body);
+    }
+}

+ 43 - 0
oss-store/src/main/java/cn/reghao/oss/store/exception/FilterExceptionHandler.java

@@ -0,0 +1,43 @@
+package cn.reghao.oss.store.exception;
+
+import cn.reghao.jutil.jdk.converter.DateTimeConverter;
+import org.springframework.boot.autoconfigure.web.ErrorProperties;
+import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
+import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 处理 filter 中抛出的异常
+ * 需要配置 server.error.path=/error
+ *
+ * @author reghao
+ * @date 2020-06-19 13:34:19
+ */
+@RestController
+public class FilterExceptionHandler extends BasicErrorController {
+    public FilterExceptionHandler() {
+        super(new DefaultErrorAttributes(), new ErrorProperties());
+    }
+
+    @Override
+    @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
+    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
+        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
+        HttpStatus status = getStatus(request);
+
+        Map<String,Object> map = new HashMap<>();
+        map.put("code",body.get("status"));
+        map.put("msg",body.get("message"));
+        map.put("timestamp", DateTimeConverter.format(System.currentTimeMillis()));
+        map.put("data",body.get("data"));
+        return ResponseEntity.status(status).body(map);
+    }
+}

+ 69 - 0
oss-store/src/main/java/cn/reghao/oss/store/inerceptor/AccessLogInterceptor.java

@@ -0,0 +1,69 @@
+package cn.reghao.oss.store.inerceptor;
+
+import cn.reghao.jutil.web.ServletUtil;
+import cn.reghao.oss.api.constant.UploadChannel;
+import cn.reghao.oss.store.config.OssProperties;
+import cn.reghao.oss.store.util.ObjectUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 访问日志拦截器
+ *
+ * @author reghao
+ * @date 2021-12-30 12:19:07
+ */
+@Slf4j
+@Component
+public class AccessLogInterceptor implements HandlerInterceptor {
+    private final String referFrom;
+
+    public AccessLogInterceptor(OssProperties ossProperties) {
+        this.referFrom = ossProperties.getReferer();
+    }
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        String uri = request.getRequestURI();
+        String method = request.getMethod();
+        if (method.equalsIgnoreCase("HEAD")) {
+            return true;
+        }
+
+        String userAgent = request.getHeader("user-agent");
+        String ipv4 = request.getRemoteAddr();
+        String referer = request.getHeader("referer");
+        String client = ServletUtil.getRequestParam("client", "");
+        if (!client.isBlank()) {
+            return true;
+        }
+
+        String objectName = uri.replaceFirst("/", "");
+        if (objectName.startsWith(UploadChannel.img.getPrefix())) {
+            return true;
+        } else if (referer == null || !referer.contains(referFrom)) {
+            log.error("request object {} from {} has been blocked", uri, referer);
+            response.setStatus(403);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void postHandle(HttpServletRequest request, HttpServletResponse response,
+                           Object handler, @Nullable ModelAndView modelAndView) throws Exception {
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
+                                Object handler, @Nullable Exception ex) throws Exception {
+    }
+}

+ 40 - 0
oss-store/src/main/java/cn/reghao/oss/store/inerceptor/MutableHttpServletRequest.java

@@ -0,0 +1,40 @@
+package cn.reghao.oss.store.inerceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.util.*;
+
+/**
+ * @author reghao
+ * @date 2022-08-25 21:45:31
+ */
+public final class MutableHttpServletRequest extends HttpServletRequestWrapper {
+    private final Map<String, String> customHeaders;
+
+    public MutableHttpServletRequest(HttpServletRequest request) {
+        super(request);
+        this.customHeaders = new HashMap<>();
+    }
+
+    public void putHeader(String name, String value) {
+        customHeaders.put(name, value);
+    }
+
+    public String getHeader(String name) {
+        String value = customHeaders.get(name);
+        if (value != null) {
+            return value;
+        }
+
+        return ((HttpServletRequest) getRequest()).getHeader(name);
+    }
+
+    public Enumeration<String> getHeaderNames() {
+        Set<String> set = new HashSet<>(customHeaders.keySet());
+        Enumeration<String> e = ((HttpServletRequest) getRequest()).getHeaderNames();
+        while (e.hasMoreElements()) {
+            set.add(e.nextElement());
+        }
+        return Collections.enumeration(set);
+    }
+}

+ 47 - 0
oss-store/src/main/java/cn/reghao/oss/store/inerceptor/TokenFilter.java

@@ -0,0 +1,47 @@
+package cn.reghao.oss.store.inerceptor;
+
+import cn.reghao.oss.store.config.OssProperties;
+import cn.reghao.oss.store.util.JwtUtil;
+import cn.reghao.oss.store.util.UserContext;
+import cn.reghao.jutil.web.ServletUtil;
+import cn.reghao.oss.api.dto.OssPayload;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.*;
+import java.io.IOException;
+
+/**
+ * @author reghao
+ * @date 2023-08-25 16:14:23
+ */
+@Component
+public class TokenFilter implements Filter {
+    private final OssProperties ossProperties;
+
+    public TokenFilter(OssProperties ossProperties) {
+        this.ossProperties = ossProperties;
+    }
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException {
+        long userId = -1L;
+        String token = ServletUtil.getBearerToken(request);
+        if (token != null) {
+            OssPayload ossPayload = JwtUtil.getOssPayload(token, ossProperties.getSecretKey());
+            userId = ossPayload.getUserId();
+        }
+
+        try (UserContext context = new UserContext(userId)) {
+            chain.doFilter(request, response);
+        }
+    }
+
+    @Override
+    public void destroy() {
+    }
+}

+ 15 - 0
oss-store/src/main/java/cn/reghao/oss/store/model/dto/ContentRange.java

@@ -0,0 +1,15 @@
+package cn.reghao.oss.store.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2022-11-28 11:27:27
+ */
+@AllArgsConstructor
+@Getter
+public class ContentRange {
+    private long start;
+    private long end;
+}

+ 18 - 0
oss-store/src/main/java/cn/reghao/oss/store/model/dto/PathUrl.java

@@ -0,0 +1,18 @@
+package cn.reghao.oss.store.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 文件的本地路径和 URL
+ *
+ * @author reghao
+ * @date 2022-04-26 15:10:04
+ */
+@AllArgsConstructor
+@Getter
+public class PathUrl {
+    private String contentId;
+    // 本地文件路径
+    private String absolutePath;
+}

+ 35 - 0
oss-store/src/main/java/cn/reghao/oss/store/model/po/AudioFile.java

@@ -0,0 +1,35 @@
+package cn.reghao.oss.store.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 音频文件
+ *
+ * @author reghao
+ * @date 2023-07-05 16:42:01
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class AudioFile extends BaseObject<Integer> {
+    private String audioFileId;
+    private String objectId;
+    private int duration;
+    private String codec;
+    private Long bitRate;
+    private String url;
+
+    public AudioFile(String audioFileId, String objectId, String url, AudioFile audioFile) {
+        this.audioFileId = audioFileId;
+        this.objectId = objectId;
+        this.duration = audioFile.getDuration();
+        this.codec = audioFile.getCodec();
+        this.bitRate = audioFile.getBitRate();
+        this.url = url;
+    }
+}

+ 38 - 0
oss-store/src/main/java/cn/reghao/oss/store/model/po/DataBlock.java

@@ -0,0 +1,38 @@
+package cn.reghao.oss.store.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2022-11-24 10:25:18
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Setter
+@Getter
+public class DataBlock extends BaseObject<Integer> {
+    private String contentId;
+    private int index;
+    private String blockId;
+    private String host;
+    private String baseDir;
+    private String relativeDir;
+    private String absolutePath;
+    private long size;
+    private long start;
+    private long end;
+    @Deprecated
+    private String objectId;
+
+    public DataBlock(String contentId, int index, String blockId, String absolutePath, long size) {
+        this.contentId = contentId;
+        this.index = index;
+        this.blockId = blockId;
+        this.absolutePath = absolutePath;
+        this.size = size;
+    }
+}

+ 78 - 0
oss-store/src/main/java/cn/reghao/oss/store/model/po/FileMeta.java

@@ -0,0 +1,78 @@
+package cn.reghao.oss.store.model.po;
+
+import cn.reghao.oss.store.util.UserContext;
+import cn.reghao.jutil.jdk.db.BaseObject;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * 文件元数据
+ *
+ * @author reghao
+ * @date 2022-11-21 10:53:10
+ */
+@NoArgsConstructor
+@Setter
+@Getter
+public class FileMeta extends BaseObject<Integer> {
+    private String objectName;
+    private String objectId;
+    private String pid;
+    private String contentId;
+    private String sha256sum;
+    private String filename;
+    private Long size;
+    private Integer fileType;
+    private String contentType;
+    private int scope;
+    private Long uploadBy;
+    private Boolean diskFile;
+
+    // 目录对象
+    public FileMeta(String objectName, String objectId, String filename, String pid, int scope) {
+        this.objectName = objectName;
+        this.objectId = objectId;
+        this.contentId = "0";
+        this.filename = filename;
+        this.size = 0L;
+        this.fileType = 1000;
+        this.contentType = "0";
+        this.sha256sum = "0";
+        this.pid = pid;
+        this.uploadBy = UserContext.getUser();
+        this.diskFile = false;
+        this.scope = scope;
+    }
+
+    public FileMeta(String objectName, String objectId, String contentId, String filename, long size,
+                    int fileType, String contentType, String sha256sum, String pid, boolean diskFile, int scope) {
+        this.objectName = objectName;
+        this.objectId = objectId;
+        this.contentId = contentId;
+        this.filename = filename;
+        this.size = size;
+        this.fileType = fileType;
+        this.contentType = contentType;
+        this.sha256sum = sha256sum;
+        this.pid = pid;
+        this.uploadBy = UserContext.getUser();
+        this.diskFile = diskFile;
+        this.scope = scope;
+    }
+
+    public FileMeta(String objectName, String objectId, String filename, FileMeta fileMeta, boolean diskFile, int scope) {
+        this.objectName = objectName;
+        this.objectId = objectId;
+        this.contentId = fileMeta.getContentId();
+        this.filename = filename;
+        this.size = fileMeta.getSize();
+        this.fileType = fileMeta.getFileType();
+        this.contentType = fileMeta.getContentType();
+        this.sha256sum = fileMeta.getSha256sum();
+        this.pid = fileMeta.getPid();
+        this.uploadBy = UserContext.getUser();
+        this.diskFile = diskFile;
+        this.scope = scope;
+    }
+}

+ 20 - 0
oss-store/src/main/java/cn/reghao/oss/store/model/po/FileType.java

@@ -0,0 +1,20 @@
+package cn.reghao.oss.store.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author reghao
+ * @date 2022-12-27 16:55:26
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+public class FileType extends BaseObject<Integer> {
+    private String code;
+    private String name;
+    private String icon;
+    private String iconLarge;
+}

+ 46 - 0
oss-store/src/main/java/cn/reghao/oss/store/model/po/ImageFile.java

@@ -0,0 +1,46 @@
+package cn.reghao.oss.store.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 图像文件
+ *
+ * @author reghao
+ * @date 2021-12-08 13:52:01
+ */
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class ImageFile extends BaseObject<Integer> {
+    // 原始文件的 objectId
+    private String imageFileId;
+    private String objectId;
+    private String format;
+    private String url;
+    private Integer width;
+    private Integer height;
+    private Boolean horizontal;
+
+    public ImageFile(String imageFileId, String objectId, String format, String url, Integer width, Integer height) {
+        this.imageFileId = imageFileId;
+        this.objectId = objectId;
+        this.format = format;
+        this.url = url;
+        this.width = width;
+        this.height = height;
+        this.horizontal = width > height;
+    }
+
+    public ImageFile(String imageFileId, String objectId, String url, ImageFile imageFile) {
+        this.imageFileId = imageFileId;
+        this.objectId = objectId;
+        this.format = imageFile.getFormat();
+        this.url = url;
+        this.width = imageFile.getWidth();
+        this.height = imageFile.getHeight();
+        this.horizontal = imageFile.getHorizontal();
+    }
+}

+ 66 - 0
oss-store/src/main/java/cn/reghao/oss/store/model/po/VideoFile.java

@@ -0,0 +1,66 @@
+package cn.reghao.oss.store.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * 视频文件
+ *
+ * @author reghao
+ * @date 2021-11-22 10:21:15
+ */
+@NoArgsConstructor
+@Setter
+@Getter
+public class VideoFile extends BaseObject<Integer> {
+    // 原始文件的 objectId
+    private String videoFileId;
+    private String objectId;
+    private String videoCodec;
+    private Long vbitRate;
+    private String audioCodec;
+    private Long abitRate;
+    private String urlType;
+    private String url;
+    private String quality;
+    private Integer width;
+    private Integer height;
+    private Boolean horizontal;
+    // 单位秒
+    private Integer duration;
+
+    public VideoFile(String videoFileId, String objectId, String videoCodec, long vbitRate, String audioCodec, long abitRate,
+                     String urlType, String url, String quality, int width, int height, int duration) {
+        this.videoFileId = videoFileId;
+        this.objectId = objectId;
+        this.videoCodec = videoCodec;
+        this.vbitRate = vbitRate;
+        this.audioCodec = audioCodec;
+        this.abitRate = abitRate;
+        this.urlType = urlType;
+        this.url = url;
+        this.quality = quality;
+        this.width = width;
+        this.height = height;
+        this.horizontal = width>height;
+        this.duration = duration;
+    }
+
+    public VideoFile(String videoFileId, String objectId, String url, VideoFile videoFile) {
+        this.videoFileId = videoFileId;
+        this.objectId = objectId;
+        this.videoCodec = videoFile.getVideoCodec();
+        this.vbitRate = videoFile.getVbitRate();
+        this.audioCodec = videoFile.getAudioCodec();
+        this.abitRate = videoFile.getAbitRate();
+        this.urlType = videoFile.getUrlType();
+        this.url = url;
+        this.quality = videoFile.getQuality();
+        this.width = videoFile.getWidth();
+        this.height = videoFile.getHeight();
+        this.horizontal = videoFile.getHorizontal();
+        this.duration = videoFile.getDuration();
+    }
+}

+ 13 - 0
oss-store/src/main/java/cn/reghao/oss/store/model/vo/ImageObject.java

@@ -0,0 +1,13 @@
+package cn.reghao.oss.store.model.vo;
+
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2023-09-10 18:02:28
+ */
+@Getter
+public class ImageObject {
+    private String imageFileId;
+    private String objectId;
+}

+ 17 - 0
oss-store/src/main/java/cn/reghao/oss/store/model/vo/ObjectProp.java

@@ -0,0 +1,17 @@
+package cn.reghao.oss.store.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2023-06-02 16:13:50
+ */
+@AllArgsConstructor
+@Getter
+public class ObjectProp {
+    private String objectName;
+    private boolean diskFile;
+    private int scope;
+    private String pid;
+}

+ 36 - 0
oss-store/src/main/java/cn/reghao/oss/store/model/vo/ObjectResult.java

@@ -0,0 +1,36 @@
+package cn.reghao.oss.store.model.vo;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2023-06-11 01:29:47
+ */
+@Setter
+@Getter
+public class ObjectResult {
+    private String objectName;
+    private String objectId;
+    private int fileType;
+    private String absolutePath;
+    private boolean duplicate;
+    private String dupObjectId;
+
+    public ObjectResult(String objectName, String objectId, int fileType, String absolutePath) {
+        this.objectName = objectName;
+        this.objectId = objectId;
+        this.fileType = fileType;
+        this.absolutePath = absolutePath;
+        this.duplicate = false;
+    }
+
+    public ObjectResult(String objectName, String objectId, int fileType, String absolutePath, String dupObjectId) {
+        this.objectName = objectName;
+        this.objectId = objectId;
+        this.fileType = fileType;
+        this.absolutePath = absolutePath;
+        this.duplicate = true;
+        this.dupObjectId = dupObjectId;
+    }
+}

+ 31 - 0
oss-store/src/main/java/cn/reghao/oss/store/rpc/ChannelServiceImpl.java

@@ -0,0 +1,31 @@
+package cn.reghao.oss.store.rpc;
+
+import cn.reghao.oss.api.constant.UploadChannel;
+import cn.reghao.oss.api.dto.ChannelDto;
+import cn.reghao.oss.api.iface.ChannelService;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-06-02 10:28:28
+ */
+@DubboService
+@Service
+public class ChannelServiceImpl implements ChannelService {
+    @Override
+    public List<ChannelDto> getChannels() {
+        List<ChannelDto> list = new ArrayList<>();
+        for (UploadChannel channel : UploadChannel.values()) {
+            int channelId = channel.getCode();
+            String channelPrefix = channel.getPrefix();
+            String channelName = channel.name();
+            list.add(new ChannelDto(channelPrefix, channelId, channelName));
+        }
+
+        return list;
+    }
+}

+ 123 - 0
oss-store/src/main/java/cn/reghao/oss/store/rpc/ObjectServiceImpl.java

@@ -0,0 +1,123 @@
+package cn.reghao.oss.store.rpc;
+
+import cn.reghao.jutil.media.ImageOps;
+import cn.reghao.oss.api.dto.*;
+import cn.reghao.oss.store.config.OssProperties;
+import cn.reghao.oss.store.db.repository.ObjectRepository;
+import cn.reghao.oss.store.model.po.FileMeta;
+import cn.reghao.oss.store.service.ObjectNameService;
+import cn.reghao.oss.store.util.JwtUtil;
+import cn.reghao.oss.store.util.SignatureUtil;
+import cn.reghao.oss.api.constant.ChannelAction;
+import cn.reghao.oss.api.constant.UploadChannel;
+import cn.reghao.oss.api.iface.ObjectService;
+import cn.reghao.oss.store.db.mapper.FileMetaMapper;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * @author reghao
+ * @date 2023-05-28 23:26:03
+ */
+@DubboService
+@Service
+public class ObjectServiceImpl implements ObjectService {
+    private final FileMetaMapper fileMetaMapper;
+    private final ObjectNameService objectNameService;
+    private final ObjectRepository objectRepository;
+    private final String secretKey;
+
+    public ObjectServiceImpl(FileMetaMapper fileMetaMapper, ObjectNameService objectNameService,
+                             ObjectRepository objectRepository, OssProperties ossProperties) {
+        this.fileMetaMapper = fileMetaMapper;
+        this.objectNameService = objectNameService;
+        this.objectRepository = objectRepository;
+        this.secretKey = ossProperties.getSecretKey();
+    }
+
+    @Override
+    public void putObjectPrefix(ObjectPrefix objectPrefix) {
+        String prefix = objectPrefix.getPrefix();
+        int scope = objectPrefix.getScope();
+        FileMeta fileMeta = fileMetaMapper.findByObjectName(prefix);
+        if (fileMeta != null) {
+            return;
+        }
+        objectNameService.createParentDirs(prefix, scope);
+    }
+
+    @Override
+    public List<String> getObjectPrefix() {
+        return fileMetaMapper.findObjectPrefix();
+    }
+
+    @Override
+    public ObjectMeta getObject(String objectName) {
+        return objectRepository.getObjectMeta(objectName);
+    }
+
+    @Override
+    public DownloadUrl getDownloadUrl(String objectId, int channelId, long userId) {
+        FileMeta fileMeta = objectRepository.getByObjectId(objectId);
+        String objectName = fileMeta.getObjectName();
+        String url = objectNameService.getObjectUrl(objectName);
+        String signedUrl = getSignedUrl(url, userId);
+        return new DownloadUrl(signedUrl, "");
+    }
+
+    private String getSignedUrl(String url, long loginUser) {
+        String secretId = loginUser+"";
+        long timestamp = System.currentTimeMillis() + 3600*1000;
+
+        String action = ChannelAction.download.getName();
+        OssPayload ossPayload = new OssPayload(action, UploadChannel.video.getCode(), loginUser);
+        String token = JwtUtil.createToken(ossPayload, timestamp, secretKey);
+        String nonce = UUID.randomUUID().toString();
+        String queryString = String.format("token=%s&t=%s&nonce=%s", token, timestamp, nonce);
+        String requestString = String.format("%s%s?%s", "GET", url, queryString);
+        String sign = SignatureUtil.sign(requestString, secretKey);
+        return String.format("%s?token=%s&t=%s&nonce=%s&sign=%s", url, token, timestamp, nonce, sign);
+    }
+
+    @Override
+    public ObjectInfo getObjectInfo(String objectId) {
+        FileMeta fileMeta = fileMetaMapper.findByObjectId(objectId);
+        if (fileMeta == null) {
+            return null;
+        }
+
+        String objectName = fileMeta.getObjectName();
+        int fileType = fileMeta.getFileType();
+        String filename = fileMeta.getFilename();
+        long size = fileMeta.getSize();
+        ObjectInfo objectInfo = new ObjectInfo(objectId, objectName, fileType, filename, size);
+        if (fileType == 1001) {
+            ObjectMeta objectMeta = objectRepository.getObjectMetaById(objectId);
+            String absolutePath = objectMeta.getAbsolutePath();
+            try {
+                ImageOps.Size resolution = ImageOps.info(new File(absolutePath));
+                objectInfo.setWidth(resolution.getWidth());
+                objectInfo.setHeight(resolution.getHeight());
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+
+        return objectInfo;
+    }
+
+    @Override
+    public ObjectUrl getObjectUrl(String objectId) {
+        FileMeta fileMeta = fileMetaMapper.findByObjectId(objectId);
+        if (fileMeta == null) {
+            return null;
+        }
+
+        return null;
+    }
+}

部分文件因文件數量過多而無法顯示