Просмотр исходного кода

Merge branch 'dev'

# Conflicts:
#	oss-api/pom.xml
#	oss-api/src/main/java/cn/reghao/oss/api/constant/ChannelAction.java
#	oss-api/src/main/java/cn/reghao/oss/api/constant/ObjectScope.java
#	oss-api/src/main/java/cn/reghao/oss/api/constant/ObjectType.java
#	oss-api/src/main/java/cn/reghao/oss/api/dto/FileInfo.java
#	oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectInfo.java
#	oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectMeta.java
#	oss-api/src/main/java/cn/reghao/oss/api/dto/OssPayload.java
#	oss-api/src/main/java/cn/reghao/oss/api/dto/media/AudioInfo.java
#	oss-api/src/main/java/cn/reghao/oss/api/dto/media/VideoInfo.java
#	oss-api/src/main/java/cn/reghao/oss/api/rest/UploadFileRet.java
#	oss-sdk/pom.xml
#	oss-sdk/src/main/java/cn/reghao/oss/sdk/ObjectMultipartUploadService.java
#	oss-store/README.md
#	oss-store/bin/oss.yml
#	oss-store/pom.xml
#	oss-store/src/main/java/cn/reghao/oss/store/config/CacheConfig.java
#	oss-store/src/main/java/cn/reghao/oss/store/config/web/WebConfig.java
#	oss-store/src/main/java/cn/reghao/oss/store/controller/ObjectGetController.java
#	oss-store/src/main/java/cn/reghao/oss/store/controller/ObjectUploadController.java
#	oss-store/src/main/java/cn/reghao/oss/store/db/mapper/DataBlockMapper.java
#	oss-store/src/main/java/cn/reghao/oss/store/db/mapper/FileMetaMapper.java
#	oss-store/src/main/java/cn/reghao/oss/store/db/repository/ObjectRepository.java
#	oss-store/src/main/java/cn/reghao/oss/store/model/po/DataBlock.java
#	oss-store/src/main/java/cn/reghao/oss/store/model/po/FileMeta.java
#	oss-store/src/main/java/cn/reghao/oss/store/model/vo/ObjectProp.java
#	oss-store/src/main/java/cn/reghao/oss/store/service/ChannelValidateService.java
#	oss-store/src/main/java/cn/reghao/oss/store/service/FileStoreService.java
#	oss-store/src/main/java/cn/reghao/oss/store/service/GetObjectService.java
#	oss-store/src/main/java/cn/reghao/oss/store/service/ObjectMultipartUploadService.java
#	oss-store/src/main/java/cn/reghao/oss/store/service/ObjectNameService.java
#	oss-store/src/main/java/cn/reghao/oss/store/service/PutObjectService.java
#	oss-store/src/main/java/cn/reghao/oss/store/util/SignatureUtil.java
#	oss-store/src/main/resources/application.yml
#	oss-store/src/main/resources/logback-spring.xml
#	oss-store/src/main/resources/mapper/DataBlockMapper.xml
#	oss-store/src/main/resources/mapper/FileMetaMapper.xml
#	oss-store/src/test/java/FileMetaTest.java
#	zzz/build.sh
reghao 1 год назад
Родитель
Сommit
b860ff634e
84 измененных файлов с 3118 добавлено и 666 удалено
  1. 11 8
      oss-api/pom.xml
  2. 2 1
      oss-api/src/main/java/cn/reghao/oss/api/constant/ChannelAction.java
  3. 10 10
      oss-api/src/main/java/cn/reghao/oss/api/constant/ObjectScope.java
  4. 10 6
      oss-api/src/main/java/cn/reghao/oss/api/constant/ObjectType.java
  5. 24 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/ChannelScopeDto.java
  6. 13 8
      oss-api/src/main/java/cn/reghao/oss/api/dto/FileInfo.java
  7. 33 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/NodeProperties.java
  8. 26 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectChannel.java
  9. 6 6
      oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectInfo.java
  10. 1 1
      oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectMeta.java
  11. 1 1
      oss-api/src/main/java/cn/reghao/oss/api/dto/OssPayload.java
  12. 27 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/StoreDiskDto.java
  13. 19 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/StoreInfo.java
  14. 26 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/StoreNodeDto.java
  15. 20 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/StoreProperties.java
  16. 27 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/UploadChannelDto.java
  17. 25 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/UploadChannelVo.java
  18. 21 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/UserNodeVO.java
  19. 8 1
      oss-api/src/main/java/cn/reghao/oss/api/dto/media/AudioInfo.java
  20. 25 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/media/ConvertedImageInfo.java
  21. 28 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/media/ImageInfo.java
  22. 16 8
      oss-api/src/main/java/cn/reghao/oss/api/dto/media/VideoInfo.java
  23. 20 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/ConsoleService.java
  24. 14 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/DiskService.java
  25. 9 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/FileService.java
  26. 18 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/ObjectChannelService.java
  27. 32 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/StoreService.java
  28. 32 0
      oss-api/src/main/java/cn/reghao/oss/api/iface/StoreServiceWrapper.java
  29. 1 5
      oss-api/src/main/java/cn/reghao/oss/api/rest/UploadFileRet.java
  30. 27 0
      oss-api/src/main/java/cn/reghao/oss/api/util/AuthContext.java
  31. 34 0
      oss-api/src/main/java/cn/reghao/oss/api/util/JwtUtil.java
  32. 5 8
      oss-sdk/pom.xml
  33. 1 1
      oss-sdk/src/main/java/cn/reghao/oss/sdk/ObjectMultipartUploadService.java
  34. 428 0
      oss-sdk/src/main/java/cn/reghao/oss/sdk/OssConsoleClient.java
  35. 267 0
      oss-sdk/src/main/java/cn/reghao/oss/sdk/OssStoreClient.java
  36. 16 0
      oss-sdk/src/main/java/cn/reghao/oss/sdk/model/OssConsoleConfig.java
  37. 64 0
      oss-sdk/src/test/java/OssConsoleClientTest.java
  38. 18 1
      oss-store/README.md
  39. 3 7
      oss-store/bin/oss.yml
  40. 25 6
      oss-store/pom.xml
  41. 48 0
      oss-store/src/main/java/cn/reghao/oss/store/config/BeansConfig.java
  42. 32 4
      oss-store/src/main/java/cn/reghao/oss/store/config/CacheConfig.java
  43. 59 0
      oss-store/src/main/java/cn/reghao/oss/store/config/SpringLifecycle.java
  44. 62 0
      oss-store/src/main/java/cn/reghao/oss/store/config/exception/ControllerExceptionHandler.java
  45. 43 0
      oss-store/src/main/java/cn/reghao/oss/store/config/exception/FilterExceptionHandler.java
  46. 74 0
      oss-store/src/main/java/cn/reghao/oss/store/config/inerceptor/AccessLogInterceptor.java
  47. 50 0
      oss-store/src/main/java/cn/reghao/oss/store/config/inerceptor/TokenFilter.java
  48. 68 0
      oss-store/src/main/java/cn/reghao/oss/store/config/mybatis/DataSourceConfig.java
  49. 83 0
      oss-store/src/main/java/cn/reghao/oss/store/config/mybatis/PageListInterceptor.java
  50. 25 0
      oss-store/src/main/java/cn/reghao/oss/store/config/props/DubboProperties.java
  51. 22 0
      oss-store/src/main/java/cn/reghao/oss/store/config/props/OssProperties.java
  52. 34 0
      oss-store/src/main/java/cn/reghao/oss/store/config/props/SpringProperties.java
  53. 2 2
      oss-store/src/main/java/cn/reghao/oss/store/config/web/WebConfig.java
  54. 45 36
      oss-store/src/main/java/cn/reghao/oss/store/controller/ObjectGetController.java
  55. 51 50
      oss-store/src/main/java/cn/reghao/oss/store/controller/ObjectUploadController.java
  56. 0 5
      oss-store/src/main/java/cn/reghao/oss/store/db/mapper/DataBlockMapper.java
  57. 5 29
      oss-store/src/main/java/cn/reghao/oss/store/db/mapper/FileMetaMapper.java
  58. 6 29
      oss-store/src/main/java/cn/reghao/oss/store/db/repository/ObjectRepository.java
  59. 8 11
      oss-store/src/main/java/cn/reghao/oss/store/model/po/DataBlock.java
  60. 8 12
      oss-store/src/main/java/cn/reghao/oss/store/model/po/FileMeta.java
  61. 0 1
      oss-store/src/main/java/cn/reghao/oss/store/model/vo/ObjectProp.java
  62. 59 0
      oss-store/src/main/java/cn/reghao/oss/store/rpc/DiskServiceImpl.java
  63. 167 0
      oss-store/src/main/java/cn/reghao/oss/store/rpc/StoreServiceImpl.java
  64. 12 51
      oss-store/src/main/java/cn/reghao/oss/store/service/ChannelValidateService.java
  65. 45 3
      oss-store/src/main/java/cn/reghao/oss/store/service/FileStoreService.java
  66. 27 19
      oss-store/src/main/java/cn/reghao/oss/store/service/GetObjectService.java
  67. 14 12
      oss-store/src/main/java/cn/reghao/oss/store/service/ObjectMultipartUploadService.java
  68. 7 32
      oss-store/src/main/java/cn/reghao/oss/store/service/ObjectNameService.java
  69. 20 13
      oss-store/src/main/java/cn/reghao/oss/store/service/PutObjectService.java
  70. 48 0
      oss-store/src/main/java/cn/reghao/oss/store/service/SignService.java
  71. 36 0
      oss-store/src/main/java/cn/reghao/oss/store/service/StoreLocalService.java
  72. 190 0
      oss-store/src/main/java/cn/reghao/oss/store/task/MediaFileProcessor.java
  73. 65 0
      oss-store/src/main/java/cn/reghao/oss/store/task/VideoFile.java
  74. 196 0
      oss-store/src/main/java/cn/reghao/oss/store/task/VideoFileProcessor.java
  75. 0 1
      oss-store/src/main/java/cn/reghao/oss/store/util/SignatureUtil.java
  76. 10 0
      oss-store/src/main/resources/application-dev.yml
  77. 11 0
      oss-store/src/main/resources/application-test.yml
  78. 6 2
      oss-store/src/main/resources/application.yml
  79. 17 35
      oss-store/src/main/resources/logback-spring.xml
  80. 4 4
      oss-store/src/main/resources/mapper/DataBlockMapper.xml
  81. 18 66
      oss-store/src/main/resources/mapper/FileMetaMapper.xml
  82. 1 162
      oss-store/src/test/java/FileMetaTest.java
  83. 30 0
      pom.xml
  84. 17 9
      zzz/build.sh

+ 11 - 8
oss-api/pom.xml

@@ -3,8 +3,12 @@
          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>
+    <parent>
+        <artifactId>oss</artifactId>
+        <groupId>cn.reghao.oss</groupId>
+        <version>1.0.0</version>
+    </parent>
 
-    <groupId>cn.reghao.oss</groupId>
     <artifactId>oss-api</artifactId>
     <version>1.0.0-SNAPSHOT</version>
 
@@ -34,12 +38,11 @@
             <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>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+    </dependencies>
 </project>

+ 2 - 1
oss-api/src/main/java/cn/reghao/oss/api/constant/ChannelAction.java

@@ -11,7 +11,8 @@ public enum ChannelAction {
     access(1, "访问"),
     download(2, "下载"),
     upload(3, "上传"),
-    delete(4, "删除");
+    delete(4, "删除"),
+    all(5, "all");
 
     private final int code;
     private final String desc;

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

@@ -4,24 +4,21 @@ 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, "验证码可见");
+    PUBLIC(2, "所有人可见");
 
     private final int code;
     private final String desc;
-
-    private static Map<Integer, String> descMap = new HashMap<>();
+    private static final Map<Integer, ObjectScope> map = new HashMap<>();
     static {
         for (ObjectScope scope : ObjectScope.values()) {
-            descMap.put(scope.code, scope.desc);
+            map.put(scope.code, scope);
         }
     }
 
@@ -49,8 +46,11 @@ public enum ObjectScope {
         return code;
     }
 
-    // TODO 第一次调用时会初始化 descMap
-    public static String getDescByCode(int code) {
-        return descMap.get(code);
+    public String getDesc() {
+        return desc;
+    }
+
+    public static ObjectScope getByCode(int code) {
+        return map.get(code);
     }
 }

+ 10 - 6
oss-api/src/main/java/cn/reghao/oss/api/constant/ObjectType.java

@@ -8,12 +8,12 @@ import java.util.Map;
  * @date 2023-06-13 15:09:09
  */
 public enum ObjectType {
-    Dir(1000, ""),
-    Image(1001, "图片"),
-    Video(1002, "视频"),
-    Audio(1003, "音频"),
-    Text(1004, "文本"),
-    Other(1005, "二进制");
+    Dir(1000, "directory"),
+    Image(1001, "image"),
+    Video(1002, "video"),
+    Audio(1003, "audio"),
+    Text(1004, "text"),
+    Other(1005, "application");
 
     private final int code;
     private final String desc;
@@ -55,6 +55,10 @@ public enum ObjectType {
         return code;
     }
 
+    public String getDesc() {
+        return desc;
+    }
+
     public static String getDescByCode(int code) {
         return descMap.get(code);
     }

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

@@ -0,0 +1,24 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2024-02-28 18:07:39
+ */
+@NoArgsConstructor
+@Setter
+@Getter
+public class ChannelScopeDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @NotNull
+    private Integer id;
+    @NotNull
+    private Integer newScope;
+}

+ 13 - 8
oss-api/src/main/java/cn/reghao/oss/api/dto/FileInfo.java

@@ -1,23 +1,28 @@
 package cn.reghao.oss.api.dto;
 
+import lombok.AllArgsConstructor;
 import lombok.Getter;
-import lombok.Setter;
 
 import java.io.Serializable;
+import java.time.LocalDateTime;
 
 /**
  * @author reghao
- * @date 2023-05-15 17:06:54
+ * @date 2024-07-11 16:42:40
  */
-@Setter
 @Getter
 public class FileInfo implements Serializable {
     private static final long serialVersionUID = 1L;
 
-    private String fileId;
+    private String objectId;
+    private String objectName;
+    private Integer fileType;
     private String filename;
-    private int type;
-    private String icon;
-    private long size;
-    private String updateTime;
+    private Long size;
+    private Integer storageType;
+    private LocalDateTime updateTime;
+
+    public FileInfo() {
+        this.storageType = 1;
+    }
 }

+ 33 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/NodeProperties.java

@@ -0,0 +1,33 @@
+package cn.reghao.oss.api.dto;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2024-03-01 09:22:17
+ */
+public class NodeProperties implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String domain;
+    private String secretKey;
+    private String referer;
+
+    public NodeProperties(String domain, String secretKey, String referer) {
+        this.domain = domain;
+        this.secretKey = secretKey;
+        this.referer = referer;
+    }
+
+    public String getDomain() {
+        return domain;
+    }
+
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    public String getReferer() {
+        return referer;
+    }
+}

+ 26 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectChannel.java

@@ -0,0 +1,26 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2024-02-26 10:08:12
+ */
+@AllArgsConstructor
+@Getter
+public class ObjectChannel implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private Integer id;
+    private Integer channelId;
+    private String prefix;
+    private Long maxSize;
+    private Integer fileType;
+    private Boolean seturl;
+    private Integer scope;
+    private String domain;
+    private Integer createBy;
+}

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

@@ -1,7 +1,6 @@
 package cn.reghao.oss.api.dto;
 
 import lombok.Getter;
-import lombok.Setter;
 
 import java.io.Serializable;
 
@@ -9,7 +8,6 @@ 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;
@@ -19,8 +17,7 @@ public class ObjectInfo implements Serializable {
     private int fileType;
     private String filename;
     private long size;
-    private int width;
-    private int height;
+    private String url;
 
     public ObjectInfo(String objectId, String objectName, int fileType, String filename, long size) {
         this.objectId = objectId;
@@ -28,7 +25,10 @@ public class ObjectInfo implements Serializable {
         this.fileType = fileType;
         this.filename = filename;
         this.size = size;
-        this.width = 0;
-        this.height = 0;
+        this.url = objectName;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
     }
 }

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

@@ -21,5 +21,5 @@ public class ObjectMeta implements Serializable {
     private long size;
     private String contentType;
     private int scope;
-    private long uploadBy;
+    private int uploadBy;
 }

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

@@ -16,5 +16,5 @@ public class OssPayload implements Serializable {
 
     private String action;
     private int channelId;
-    private Long userId;
+    private int userId;
 }

+ 27 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/StoreDiskDto.java

@@ -0,0 +1,27 @@
+package cn.reghao.oss.api.dto;
+
+import cn.reghao.jutil.jdk.store.LocalStore;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2024-03-04 17:12:21
+ */
+@NoArgsConstructor
+@Getter
+public class StoreDiskDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String diskDir;
+    private Long total;
+    private Long avail;
+
+    public StoreDiskDto(LocalStore localStore) {
+        this.diskDir = localStore.getDiskDir();
+        this.total = localStore.getTotal();
+        this.avail = localStore.getAvailable();
+    }
+}

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

@@ -0,0 +1,19 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-03-07 14:00:22
+ */
+@AllArgsConstructor
+@Getter
+public class StoreInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private List<StoreDiskDto> storeDisks;
+}

+ 26 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/StoreNodeDto.java

@@ -0,0 +1,26 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-03-04 17:12:14
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Setter
+@Getter
+public class StoreNodeDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String nodeAddr;
+    private Integer httpPort;
+    private Integer rpcPort;
+    private List<StoreDiskDto> diskDtoList;
+}

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

@@ -0,0 +1,20 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2024-03-01 09:22:17
+ */
+@AllArgsConstructor
+@Getter
+public class StoreProperties implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String domain;
+    private String secretKey;
+    private String referer;
+}

+ 27 - 0
oss-api/src/main/java/cn/reghao/oss/api/dto/UploadChannelDto.java

@@ -0,0 +1,27 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2021-06-03 16:15:23
+ */
+@Data
+public class UploadChannelDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @NotNull
+    private Integer nodeId;
+    @NotBlank
+    private String channelPrefix;
+    @NotNull
+    private Long maxSize;
+    @NotNull
+    private Integer fileType;
+    @NotNull
+    private Integer scope;
+}

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

@@ -0,0 +1,25 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2024-02-28 13:16:02
+ */
+@AllArgsConstructor
+@Getter
+public class UploadChannelVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private int id;
+    private int channelId;
+    private String prefix;
+    private String maxSize;
+    private String fileType;
+    private boolean seturl;
+    private String scope;
+    private String bindDomain;
+}

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

@@ -0,0 +1,21 @@
+package cn.reghao.oss.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2024-07-02 16:18:14
+ */
+@AllArgsConstructor
+@Getter
+public class UserNodeVO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private int id;
+    private int nodeId;
+    private String nodeAddr;
+    private String domain;
+}

+ 8 - 1
oss-api/src/main/java/cn/reghao/oss/api/dto/media/AudioInfo.java

@@ -1,5 +1,6 @@
 package cn.reghao.oss.api.dto.media;
 
+import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 
@@ -9,13 +10,19 @@ import java.io.Serializable;
  * @author reghao
  * @date 2023-08-28 17:00:56
  */
-@NoArgsConstructor
+@AllArgsConstructor
 @Getter
 public class AudioInfo implements Serializable {
     private static final long serialVersionUID = 1L;
 
     private String audioFileId;
+    private String objectId;
     private int duration;
     private String codec;
+    private Long bitRate;
     private String url;
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
 }

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

@@ -0,0 +1,25 @@
+package cn.reghao.oss.api.dto.media;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2024-03-08 17:07:23
+ */
+@AllArgsConstructor
+@Getter
+public class ConvertedImageInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String imageFileId;
+    private String objectId;
+    private String format;
+    private String url;
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+}

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

@@ -0,0 +1,28 @@
+package cn.reghao.oss.api.dto.media;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2024-03-08 10:56:27
+ */
+@AllArgsConstructor
+@Getter
+public class ImageInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    // 原始文件的 objectId
+    private String imageFileId;
+    private String objectId;
+    private String format;
+    private String url;
+    private Integer width;
+    private Integer height;
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+}

+ 16 - 8
oss-api/src/main/java/cn/reghao/oss/api/dto/media/VideoInfo.java

@@ -1,28 +1,36 @@
 package cn.reghao.oss.api.dto.media;
 
+import lombok.AllArgsConstructor;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
 
 import java.io.Serializable;
+import java.time.LocalDateTime;
 
 /**
  * @author reghao
  * @date 2023-01-11 10:41:53
  */
-@NoArgsConstructor
-@Setter
+@AllArgsConstructor
 @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 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 Integer duration;
+    private LocalDateTime createTime;
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
 }

+ 20 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/ConsoleService.java

@@ -0,0 +1,20 @@
+package cn.reghao.oss.api.iface;
+
+import cn.reghao.oss.api.dto.NodeProperties;
+import cn.reghao.oss.api.dto.ObjectChannel;
+import cn.reghao.oss.api.dto.ServerInfo;
+import cn.reghao.oss.api.dto.StoreNodeDto;
+
+/**
+ * 获取 StoreNode 配置
+ * oss-store 调用
+ * @author reghao
+ * @date 2024-07-02 10:55:56
+ */
+public interface ConsoleService {
+    void registerNode(StoreNodeDto storeNodeDto);
+    NodeProperties getNodeProperties(String domain);
+    ObjectChannel getChannelById(int owner, int channelId);
+    Integer getChannelIdByUrl(int loginUser, String url);
+    ServerInfo getUploadStore(int loginUser, int channelId);
+}

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

@@ -0,0 +1,14 @@
+package cn.reghao.oss.api.iface;
+
+import cn.reghao.oss.api.dto.FileInfo;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-07-11 16:45:54
+ */
+public interface DiskService {
+    List<FileInfo> getFiles(String pid, int pageSize, String nextObjectId);
+}

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

@@ -0,0 +1,9 @@
+package cn.reghao.oss.api.iface;
+
+/**
+ * @author reghao
+ * @date 2024-07-07 16:55:29
+ */
+public interface FileService {
+    String getAccountAvatar(long userId);
+}

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

@@ -0,0 +1,18 @@
+package cn.reghao.oss.api.iface;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.oss.api.dto.*;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-07-12 22:16:31
+ */
+public interface ObjectChannelService {
+    Result addObjectChannel(UploadChannelDto uploadChannelDto);
+    void updateScope(ChannelScopeDto channelScopeDto);
+    List<UploadChannelVo> getObjectChannels(int createBy);
+    ObjectChannel getObjectChannel(int id);
+    List<UserNodeVO> getUserStoreNodes(int createBy);
+}

+ 32 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/StoreService.java

@@ -0,0 +1,32 @@
+package cn.reghao.oss.api.iface;
+
+import cn.reghao.oss.api.dto.ObjectInfo;
+import cn.reghao.oss.api.dto.StoreInfo;
+import cn.reghao.oss.api.dto.media.AudioInfo;
+import cn.reghao.oss.api.dto.media.ConvertedImageInfo;
+import cn.reghao.oss.api.dto.media.ImageInfo;
+import cn.reghao.oss.api.dto.media.VideoInfo;
+
+/**
+ * 对 oss-store 进行操作
+ * oss-console 调用
+ *
+ * @author reghao
+ * @date 2023-08-01 14:51:50
+ */
+public interface StoreService {
+    void createChannel(String channelPrefix);
+    StoreInfo getStoreInfo();
+    String getUploadToken(int channelId, int loginUser, int expire);
+
+    void setObjectScope(String objectId, int scope);
+    void deleteByObjectId(String objectId);
+    void deleteByObjectUrl(String objectUrl);
+    ObjectInfo getObjectInfo(String objectId);
+    String getSignedUrl(String domain, int loginUser, String objectId, int expire);
+
+    VideoInfo getVideoInfo(String objectId);
+    ImageInfo getImageInfo(String objectId);
+    ConvertedImageInfo getWebpInfo(String objectId);
+    AudioInfo getAudioInfo(String objectId);
+}

+ 32 - 0
oss-api/src/main/java/cn/reghao/oss/api/iface/StoreServiceWrapper.java

@@ -0,0 +1,32 @@
+package cn.reghao.oss.api.iface;
+
+import cn.reghao.oss.api.dto.ObjectInfo;
+import cn.reghao.oss.api.dto.StoreInfo;
+import cn.reghao.oss.api.dto.media.AudioInfo;
+import cn.reghao.oss.api.dto.media.ConvertedImageInfo;
+import cn.reghao.oss.api.dto.media.ImageInfo;
+import cn.reghao.oss.api.dto.media.VideoInfo;
+
+/**
+ * 对 oss-store 进行操作
+ * 第三方通过 oss-console 调用
+ *
+ * @author reghao
+ * @date 2024-04-23 15:14:06
+ */
+public interface StoreServiceWrapper {
+    void createChannel(int channelId, String channelPrefix);
+    StoreInfo getStoreInfo(int channelId);
+
+    Integer getChannelScope(int channelId);
+    void setObjectScope(int channelId, String objectId, int scope);
+    void deleteByObjectId(int channelId, String objectId);
+    void deleteByObjectUrl(String objectUrl);
+    ObjectInfo getObjectInfo(int channelId, String objectId);
+    String getSignedUrl(int channelId, String objectId);
+
+    VideoInfo getVideoInfo(int channelId, String objectId);
+    ImageInfo getImageInfo(int channelId, String objectId);
+    ConvertedImageInfo getWebpInfo(int channelId, String objectId);
+    AudioInfo getAudioInfo(int channelId, String objectId);
+}

+ 1 - 5
oss-api/src/main/java/cn/reghao/oss/api/rest/UploadFileRet.java

@@ -13,7 +13,7 @@ public class UploadFileRet implements Serializable {
     private static final long serialVersionUID = 1L;
 
     private final String uploadId;
-    private String url;
+    private final String url;
     private final boolean merged;
 
     public UploadFileRet(String uploadId) {
@@ -27,8 +27,4 @@ public class UploadFileRet implements Serializable {
         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/util/AuthContext.java

@@ -0,0 +1,27 @@
+package cn.reghao.oss.api.util;
+
+/**
+ * @author reghao
+ * @date 2023-06-02 10:48:59
+ */
+public class AuthContext implements AutoCloseable {
+    static final ThreadLocal<Integer> CURRENT = new ThreadLocal<>();
+
+    public AuthContext(int user) {
+        CURRENT.set(user);
+    }
+
+    @Deprecated
+    public static int getUser() {
+        return CURRENT.get();
+    }
+
+    public static int getUserId() {
+        return CURRENT.get();
+    }
+
+    @Override
+    public void close() {
+        CURRENT.remove();
+    }
+}

+ 34 - 0
oss-api/src/main/java/cn/reghao/oss/api/util/JwtUtil.java

@@ -0,0 +1,34 @@
+package cn.reghao.oss.api.util;
+
+import cn.reghao.oss.api.dto.OssPayload;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+import java.util.Date;
+
+/**
+ * JWT 令牌
+ *
+ * @author reghao
+ * @date 2023-08-23 09:10:58
+ */
+public class JwtUtil {
+    public static String createToken(OssPayload ossPayload, long expireAt, String signKey) {
+        return Jwts.builder()
+                .claim("action", ossPayload.getAction())
+                .claim("channelId", ossPayload.getChannelId())
+                .setSubject(String.valueOf(ossPayload.getUserId()))
+                .setExpiration(new Date(expireAt))
+                .signWith(SignatureAlgorithm.HS256, signKey)
+                .compact();
+    }
+
+    public static OssPayload getOssPayload(String token, String signKey) {
+        Claims claims = Jwts.parser().setSigningKey(signKey).parseClaimsJws(token).getBody();
+        String action = (String) claims.get("action");
+        int channelId = (Integer) claims.get("channelId");
+        String userIdStr = claims.getSubject();
+        return new OssPayload(action, channelId, Integer.parseInt(userIdStr));
+    }
+}

+ 5 - 8
oss-sdk/pom.xml

@@ -3,8 +3,12 @@
          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>
+    <parent>
+        <artifactId>oss</artifactId>
+        <groupId>cn.reghao.oss</groupId>
+        <version>1.0.0</version>
+    </parent>
 
-    <groupId>cn.reghao.oss</groupId>
     <artifactId>oss-sdk</artifactId>
     <version>1.0.0-SNAPSHOT</version>
 
@@ -50,11 +54,4 @@
             <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>

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

@@ -140,7 +140,7 @@ public class ObjectMultipartUploadService {
         uploadParam.setTextParams(params);
 
         String api = endpoint + "/?multipart";
-        WebResponse webResponse = webRequest.upload(api, uploadParam);
+        WebResponse webResponse = webRequest.upload(api, uploadParam, null);
         int statusCode = webResponse.getStatusCode();
         if (statusCode != 200) {
             log.error("请求失败");

+ 428 - 0
oss-sdk/src/main/java/cn/reghao/oss/sdk/OssConsoleClient.java

@@ -0,0 +1,428 @@
+package cn.reghao.oss.sdk;
+
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
+import cn.reghao.oss.sdk.model.OssConsoleConfig;
+import cn.reghao.oss.api.dto.ObjectInfo;
+import cn.reghao.oss.api.dto.ServerInfo;
+import cn.reghao.oss.api.dto.media.*;
+import cn.reghao.oss.api.rest.UploadFileRet;
+import com.google.gson.reflect.TypeToken;
+
+import java.io.File;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 21:47:39
+ */
+public class OssConsoleClient {
+    private final HttpClient httpClient = HttpClient.newBuilder().build();
+    private final String endpoint;
+    private String token;
+
+    public OssConsoleClient(OssConsoleConfig ossProperties) throws Exception {
+        this.endpoint = ossProperties.getConsoleUrl();
+        auth(ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret());
+    }
+
+    public OssConsoleClient(String endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    // ****************************************************************************************************************
+    // oss-console 认证接口
+    // ****************************************************************************************************************
+    private void auth(String accessKeyId, String accessKeySecret) throws Exception {
+        MultiPartBodyPublisher publisher = new MultiPartBodyPublisher()
+                .addPart("accessKeyId", accessKeyId)
+                .addPart("accessKeySecret", accessKeySecret);
+
+        String api = String.format("%s/api/admin/oss/key/auth", endpoint);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .version(HttpClient.Version.HTTP_1_1)
+                .header("content-type", "multipart/form-data; boundary=" + publisher.getBoundary())
+                .POST(publisher.build())
+                .build();
+
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        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<String>>(){}.getType();
+        WebResult<String> webResult = JsonConverter.jsonToObject(body, type);
+        if (webResult.getCode() != 0) {
+            String errMsg = String.format("%s - %s", webResult.getCode(), webResult.getMsg());
+            throw new Exception(errMsg);
+        }
+
+        this.token = webResult.getData();
+    }
+
+    // ****************************************************************************************************************
+    // oss-store 相关接口
+    // ****************************************************************************************************************
+    public ServerInfo getUploadStore(int channelId) throws Exception {
+        String api = String.format("%s/api/admin/oss/upload/store?channelId=%s", endpoint, channelId);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .header("authorization", "Bearer " + token)
+                .version(HttpClient.Version.HTTP_1_1)
+                .GET()
+                .build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        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<ServerInfo>>(){}.getType();
+        WebResult<ServerInfo> 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();
+    }
+
+    public Integer getChannelScope(int channelId) throws Exception {
+        String api = String.format("%s/api/admin/oss/channel/scope?channelId=%s", endpoint, channelId);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .header("authorization", "Bearer " + token)
+                .version(HttpClient.Version.HTTP_1_1)
+                .GET()
+                .build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        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<Integer>>(){}.getType();
+        WebResult<Integer> 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();
+    }
+
+    public void setObjectScope(int channelId, String objectId, int scope) throws Exception {
+        Map<String, String> formData = new HashMap<>();
+        formData.put("channelId", channelId+"");
+        formData.put("scope", scope+"");
+        formData.put("objectId", objectId);
+
+        String api = String.format("%s/api/admin/oss/object/scope", endpoint);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .header("authorization", "Bearer " + token)
+                .headers("content-type", "application/x-www-form-urlencoded")
+                .version(HttpClient.Version.HTTP_1_1)
+                .POST(HttpRequest.BodyPublishers.ofString(getFormDataAsString(formData)))
+                .build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        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<String>>(){}.getType();
+        WebResult<String> webResult = JsonConverter.jsonToObject(body, type);
+        if (webResult.getCode() != 0) {
+            String errMsg = String.format("%s - %s", webResult.getCode(), webResult.getMsg());
+            throw new Exception(errMsg);
+        }
+    }
+
+    private String getFormDataAsString(Map<String, String> formData) {
+        StringBuilder formBodyBuilder = new StringBuilder();
+        for (Map.Entry<String, String> singleEntry : formData.entrySet()) {
+            if (formBodyBuilder.length() > 0) {
+                formBodyBuilder.append("&");
+            }
+            formBodyBuilder.append(URLEncoder.encode(singleEntry.getKey(), StandardCharsets.UTF_8));
+            formBodyBuilder.append("=");
+            formBodyBuilder.append(URLEncoder.encode(singleEntry.getValue(), StandardCharsets.UTF_8));
+        }
+        return formBodyBuilder.toString();
+    }
+
+    public void deleteByObjectId(int channelId, String objectId) throws Exception {
+        Map<String, String> formData = new HashMap<>();
+        formData.put("channelId", channelId+"");
+        formData.put("objectId", objectId);
+
+        String api = String.format("%s/api/admin/oss/object/delete/id", endpoint);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .header("authorization", "Bearer " + token)
+                .headers("content-type", "application/x-www-form-urlencoded")
+                .version(HttpClient.Version.HTTP_1_1)
+                .POST(HttpRequest.BodyPublishers.ofString(getFormDataAsString(formData)))
+                .build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        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<String>>(){}.getType();
+        WebResult<String> webResult = JsonConverter.jsonToObject(body, type);
+        if (webResult.getCode() != 0) {
+            String errMsg = String.format("%s - %s", webResult.getCode(), webResult.getMsg());
+            throw new Exception(errMsg);
+        }
+    }
+
+    public void deleteByObjectUrl(String objectUrl) throws Exception {
+        Map<String, String> formData = new HashMap<>();
+        formData.put("objectUrl", objectUrl);
+
+        String api = String.format("%s/api/admin/oss/object/delete/url", endpoint);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .header("authorization", "Bearer " + token)
+                .headers("content-type", "application/x-www-form-urlencoded")
+                .version(HttpClient.Version.HTTP_1_1)
+                .POST(HttpRequest.BodyPublishers.ofString(getFormDataAsString(formData)))
+                .build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        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<String>>(){}.getType();
+        WebResult<String> webResult = JsonConverter.jsonToObject(body, type);
+        if (webResult.getCode() != 0) {
+            String errMsg = String.format("%s - %s", webResult.getCode(), webResult.getMsg());
+            throw new Exception(errMsg);
+        }
+    }
+
+    public ObjectInfo getObjectInfo(int channelId, String objectId) throws Exception {
+        String api = String.format("%s/api/admin/oss/object/info?channelId=%s&objectId=%s", endpoint, channelId, objectId);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .header("authorization", "Bearer " + token)
+                .version(HttpClient.Version.HTTP_1_1)
+                .GET()
+                .build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        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<ObjectInfo>>(){}.getType();
+        WebResult<ObjectInfo> 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();
+    }
+
+    public String getSignedUrl(int channelId, String objectId) throws Exception {
+        String api = String.format("%s/api/admin/oss/object/url?channelId=%s&objectId=%s", endpoint, channelId, objectId);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .header("authorization", "Bearer " + token)
+                .version(HttpClient.Version.HTTP_1_1)
+                .GET()
+                .build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        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<String>>(){}.getType();
+        WebResult<String> 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();
+    }
+
+    public VideoInfo getVideoInfo(int channelId, String objectId) throws Exception {
+        String api = String.format("%s/api/admin/oss/object/video/info?channelId=%s&objectId=%s", endpoint, channelId, objectId);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .header("authorization", "Bearer " + token)
+                .version(HttpClient.Version.HTTP_1_1)
+                .GET()
+                .build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        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<VideoInfo>>(){}.getType();
+        WebResult<VideoInfo> 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();
+    }
+
+    public ImageInfo getImageInfo(int channelId, String objectId) throws Exception {
+        String api = String.format("%s/api/admin/oss/object/image/info?channelId=%s&objectId=%s", endpoint, channelId, objectId);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .header("authorization", "Bearer " + token)
+                .headers("content-type", "application/x-www-form-urlencoded")
+                .version(HttpClient.Version.HTTP_1_1)
+                .GET()
+                .build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        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<ImageInfo>>(){}.getType();
+        WebResult<ImageInfo> 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();
+    }
+
+    public ConvertedImageInfo getWebpInfo(int channelId, String objectId) throws Exception {
+        Map<String, String> formData = new HashMap<>();
+        formData.put("channelId", ""+channelId);
+        formData.put("objectId", objectId);
+
+        String api = String.format("%s/api/admin/oss/object/image/webp", endpoint);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .header("authorization", "Bearer " + token)
+                .headers("content-type", "application/x-www-form-urlencoded")
+                .version(HttpClient.Version.HTTP_1_1)
+                .POST(HttpRequest.BodyPublishers.ofString(getFormDataAsString(formData)))
+                .build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        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<ConvertedImageInfo>>(){}.getType();
+        WebResult<ConvertedImageInfo> 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();
+    }
+
+    public AudioInfo getAudioInfo(int channelId, String objectId) throws Exception {
+        String api = String.format("%s/api/admin/oss/object/audio/info/?channelId=%s&objectId=%s", endpoint, channelId, objectId);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .header("authorization", "Bearer " + token)
+                .version(HttpClient.Version.HTTP_1_1)
+                .GET()
+                .build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        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<AudioInfo>>(){}.getType();
+        WebResult<AudioInfo> 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();
+    }
+
+    // ****************************************************************************************************************
+    // 音视频转码相关接口
+    // ****************************************************************************************************************
+    public void convertAudio(String audioFileId) throws Exception {
+        String api = String.format("%s/api/admin/oss/object/convert/audio/%s", endpoint, audioFileId);
+        convert(api);
+    }
+
+    public void convertVideo(String videoFileId) throws Exception {
+        String api = String.format("%s/api/admin/oss/object/convert/video/%s", endpoint, videoFileId);
+        convert(api);
+    }
+
+    private void convert(String api) throws Exception {
+        MultiPartBodyPublisher publisher = new MultiPartBodyPublisher();
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .header("authorization", "Bearer " + token)
+                .version(HttpClient.Version.HTTP_1_1)
+                .POST(publisher.build())
+                .build();
+        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+        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<String>>(){}.getType();
+        WebResult<String> webResult = JsonConverter.jsonToObject(body, type);
+        if (webResult.getCode() != 0) {
+            String errMsg = String.format("%s - %s", webResult.getCode(), webResult.getMsg());
+            throw new Exception(errMsg);
+        }
+    }
+
+    public UploadFileRet uploadFile(int channelId, File file) throws Exception {
+        ServerInfo serverInfo = getUploadStore(channelId);
+        if (serverInfo == null) {
+            throw new Exception("获取 server_info 失败");
+        }
+        String ossUrl = serverInfo.getOssUrl();
+        String token = serverInfo.getToken();
+
+        OssStoreClient ossStoreClient = new OssStoreClient(ossUrl);
+        UploadFileRet uploadFileRet = ossStoreClient.postObjectWithJdkHttp(file, channelId, token);
+        return uploadFileRet;
+    }
+}

+ 267 - 0
oss-sdk/src/main/java/cn/reghao/oss/sdk/OssStoreClient.java

@@ -0,0 +1,267 @@
+package cn.reghao.oss.sdk;
+
+import cn.reghao.jutil.jdk.http.UploadParam;
+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 org.apache.commons.io.FileUtils;
+
+import java.io.BufferedInputStream;
+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;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+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 OssStoreClient {
+    private final String endpoint;
+    private final HttpClient httpClient = HttpClient.newBuilder().build();
+    private final DefaultWebRequest webRequest = new DefaultWebRequest();
+    private final String baseDir = "/opt/tmp/";
+
+    public OssStoreClient(String endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    public UploadFileRet putObject(File file, int channelId, int 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, String token) throws Exception {
+        String sha256sum = DigestUtil.sha256sum(file.getAbsolutePath());
+        MultiPartBodyPublisher publisher = new MultiPartBodyPublisher()
+                .addPart("file", Paths.get(file.getAbsolutePath()))
+                .addPart("client", "oss-sdk-1.0")
+                .addPart("sha256sum", sha256sum)
+                .addPart("channelId", channelId+"");
+
+        String api = String.format("%s/", endpoint);
+        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
+                .version(HttpClient.Version.HTTP_1_1)
+                .header("authorization", "Bearer " + token)
+                .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, String token) throws Exception {
+        String sha256sum = DigestUtil.sha256sum(file.getAbsolutePath());
+        Map<String, String> map = new HashMap<>();
+        map.put("channelId", ""+channelId);
+        map.put("client", "client");
+        map.put("sha256sum", sha256sum);
+        UploadParam uploadParam = new UploadParam(file, map);
+
+        String api = String.format("%s/", endpoint);
+        WebResponse webResponse = webRequest.upload(api, uploadParam, token);
+        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();
+    }
+
+    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, String filePath) {
+        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(filePath, httpResponse.body());
+                log.info("saved to {}", localPath);
+            } else {
+                log.error("{}", statusCode);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public String saveFile(String filePath, InputStream in) {
+        File file = new File(filePath);
+        File parentDir = file.getParentFile();
+        try {
+            if (!parentDir.exists()) {
+                FileUtils.forceMkdir(parentDir);
+            }
+
+            FileOutputStream fos = new FileOutputStream(file);
+            // 1MB
+            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 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();
+        }
+    }
+
+    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);
+            // 1MB
+            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;
+    }
+}

+ 16 - 0
oss-sdk/src/main/java/cn/reghao/oss/sdk/model/OssConsoleConfig.java

@@ -0,0 +1,16 @@
+package cn.reghao.oss.sdk.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2024-04-19 20:01:46
+ */
+@AllArgsConstructor
+@Getter
+public class OssConsoleConfig {
+    private String consoleUrl;
+    private String accessKeyId;
+    private String accessKeySecret;
+}

+ 64 - 0
oss-sdk/src/test/java/OssConsoleClientTest.java

@@ -0,0 +1,64 @@
+import cn.reghao.oss.sdk.model.OssConsoleConfig;
+import cn.reghao.oss.api.dto.ServerInfo;
+import cn.reghao.oss.api.rest.UploadFileRet;
+import cn.reghao.oss.sdk.ObjectMultipartUploadService;
+import cn.reghao.oss.sdk.OssStoreClient;
+import cn.reghao.oss.sdk.OssConsoleClient;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.*;
+
+/**
+ * @author reghao
+ * @date 2023-06-02 16:19:04
+ */
+@Slf4j
+public class OssConsoleClientTest {
+    static void multipartUpload() throws Exception {
+        String storeEndpoint = "";
+        ObjectMultipartUploadService multipartUploadService = new ObjectMultipartUploadService(storeEndpoint);
+        String filePath = "";
+        int channelId = 1;
+        multipartUploadService.upload(new File(filePath), channelId);
+        //multipartUploadService.create();
+        //multipartUploadService.get();
+    }
+
+    public static void main(String[] args) throws Exception {
+        String consoleUrl = "http://ossconsole.reghao.cn";
+        String accessKeyId = "mWYMpbrL";
+        String accessKeySecret = "uvuTJKKbRfaSxNPM38";
+        OssConsoleConfig ossProperties = new OssConsoleConfig(consoleUrl, accessKeyId, accessKeySecret);
+        OssConsoleClient ossConsoleClient = new OssConsoleClient(ossProperties);
+
+        int channelId = 101;
+        ServerInfo serverInfo = ossConsoleClient.getUploadStore(channelId);
+        if (serverInfo == null) {
+            log.info("获取 server_info 失败");
+            return;
+        }
+        String ossUrl = serverInfo.getOssUrl();
+        String token = serverInfo.getToken();
+
+        OssStoreClient ossStoreClient = new OssStoreClient(ossUrl);
+        String filePath = "/home/reghao/data/video/output.mp4";
+        File file = new File(filePath);
+        UploadFileRet uploadFileRet = ossStoreClient.postObjectWithJdkHttp(file, channelId, token);
+        if (uploadFileRet == null) {
+            log.info("文件上传失败");
+        } else {
+            log.info("{} -> {}", uploadFileRet.getUploadId(), uploadFileRet.getUrl());
+        }
+
+        /*String objectName = "video/playback/28d0fd95e224499c9f2cf1d98b4551a5.flv";
+        ossStoreClient.getObject(objectName);*/
+
+        /*String sha256sum = "1234567890";
+        storeClient.headObject1(sha256sum);*/
+
+        /*int scope = 1;
+        String objectId = "dafafafa";
+        int contentType = 1001;
+        ossConsoleClient.setObjectScope(scope, objectId, contentType);*/
+    }
+}

+ 18 - 1
oss-store/README.md

@@ -3,4 +3,21 @@
 
 ## 依赖的第三方服务
 - mysql
-- zookeeper
+
+## 设计思路
+- oss-store
+> 存储节点
+- oss-console
+> 管理存储节点
+
+
+```
+store 启动时注册到 console -> 用户选择 store -> 用户配置 store 
+-> 用户在 store 上创建上传 channel -> 用户通过 channelId+accessKey+accessSecret 上传和访问文件
+``` 
+
+### 访问权限
+每个文件对象有一个作用域, 非 public 作用域的对象需要获取一个签名 url 后才可访问
+
+### 存储
+重写 HttpMessageConverter, 实现将上传的文件直接存储指定位置, 在后续逻辑中通过 sha256sum 值来判断是否删除文件

+ 3 - 7
oss-store/bin/oss.yml

@@ -1,17 +1,13 @@
 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
+  storeHost: 127.0.0.1
   diskDirs:
-    - /opt/oss/disk/13f654c8-af87-4710-aac9-7aa086c99aec/
-  referer: reghao.cn
-  secretKey: oss-store
+    - /opt/disk/13f654c8-af87-4710-aac9-7aa086c99aec/
+  consoleHost: 127.0.0.1

+ 25 - 6
oss-store/pom.xml

@@ -3,10 +3,15 @@
          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>
+    <parent>
+        <artifactId>oss</artifactId>
+        <groupId>cn.reghao.oss</groupId>
+        <version>1.0.0</version>
+    </parent>
 
-    <groupId>cn.reghao.oss</groupId>
     <artifactId>oss-store</artifactId>
     <version>1.0.0</version>
+    <packaging>jar</packaging>
 
     <properties>
         <maven.compiler.source>11</maven.compiler.source>
@@ -119,11 +124,6 @@
             <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>
@@ -158,6 +158,24 @@
         </dependency>
     </dependencies>
 
+    <profiles>
+        <profile>
+            <id>dev</id>
+            <properties>
+                <profile.active>dev</profile.active>
+            </properties>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+        <profile>
+            <id>test</id>
+            <properties>
+                <profile.active>test</profile.active>
+            </properties>
+        </profile>
+    </profiles>
+
     <build>
         <finalName>oss-store</finalName>
         <resources>
@@ -166,6 +184,7 @@
                 <filtering>true</filtering>
                 <includes>
                     <include>application.yml</include>
+                    <include>application-${profile.active}.yml</include>
                     <include>mapper/**</include>
                     <include>*.xml</include>
                 </includes>

+ 48 - 0
oss-store/src/main/java/cn/reghao/oss/store/config/BeansConfig.java

@@ -0,0 +1,48 @@
+package cn.reghao.oss.store.config;
+
+import cn.reghao.oss.api.iface.ConsoleService;
+import cn.reghao.oss.store.config.props.OssProperties;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ConsumerConfig;
+import org.apache.dubbo.config.ReferenceConfig;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author reghao
+ * @date 2024-02-28 11:11:04
+ */
+@Configuration
+public class BeansConfig {
+    @Bean
+    public ConsoleService nodeService(OssProperties ossProperties) {
+        RemoteService<ConsoleService> remoteService = new RemoteService<>();
+        String host = ossProperties.getConsoleHost();
+        int port = 14030;
+        return remoteService.getService(host, port, ConsoleService.class);
+    }
+
+    static class RemoteService<T> {
+        public T getService(String host, int port, Class<T> clazz) {
+            String serviceName = "remote-service";
+            String dubboUrl = String.format("dubbo://%s:%s/%s", host, port, clazz.getName());
+
+            // 当前应用配置
+            ApplicationConfig application = new ApplicationConfig();
+            application.setName(serviceName);
+
+            ConsumerConfig consumerConfig = new ConsumerConfig();
+            consumerConfig.setTimeout(60_000);
+
+            // 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接
+            // 引用远程服务
+            // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
+            ReferenceConfig<T> reference = new ReferenceConfig<>();
+            reference.setApplication(application);
+            reference.setInterface(clazz);
+            reference.setUrl(dubboUrl);
+            reference.setConsumer(consumerConfig);
+            return reference.get();
+        }
+    }
+}

+ 32 - 4
oss-store/src/main/java/cn/reghao/oss/store/config/CacheConfig.java

@@ -1,5 +1,6 @@
 package cn.reghao.oss.store.config;
 
+import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import org.springframework.cache.CacheManager;
 import org.springframework.cache.annotation.EnableCaching;
@@ -19,6 +20,13 @@ import java.util.concurrent.TimeUnit;
 @EnableCaching
 @Configuration
 public class CacheConfig {
+    /**
+     * 为 Spring Cache 的相关注解提供一个缓存
+     *
+     * @param
+     * @return
+     * @date 2024-03-04 16:37:05
+     */
     @Bean
     public CacheManager cacheManager() {
         CaffeineCacheManager cacheManager = new CaffeineCacheManager();
@@ -30,11 +38,31 @@ public class CacheConfig {
         return cacheManager;
     }
 
-    /*@Bean("caffeineCache")
-    public Caffeine<Object, Object> caffeineCache() {
+    /**
+     * 提供一个全局的独立缓存
+     *
+     * @param
+     * @return
+     * @date 2024-03-04 16:37:38
+     */
+    @Bean("caffeineCache")
+    public Cache<String, Object> caffeineCache() {
         return Caffeine.newBuilder()
                 .initialCapacity(1000)
                 .maximumSize(10_000)
-                .expireAfterAccess(365, TimeUnit.DAYS);
-    }*/
+                .expireAfterAccess(365, TimeUnit.DAYS)
+                .build();
+    }
+
+    @Bean
+    public Cache<String, String> caffeineCache1() {
+        return Caffeine.newBuilder()
+                // 设置最后一次写入或访问后经过固定时间过期
+                .expireAfterWrite(30, TimeUnit.DAYS)
+                // 初始的缓存空间大小
+                .initialCapacity(10_000)
+                // 缓存的最大条数
+                .maximumSize(100_000)
+                .build();
+    }
 }

+ 59 - 0
oss-store/src/main/java/cn/reghao/oss/store/config/SpringLifecycle.java

@@ -0,0 +1,59 @@
+package cn.reghao.oss.store.config;
+
+import cn.reghao.oss.store.config.props.DubboProperties;
+import cn.reghao.oss.api.dto.StoreDiskDto;
+import cn.reghao.oss.api.dto.StoreNodeDto;
+import cn.reghao.oss.store.config.props.SpringProperties;
+import cn.reghao.oss.store.service.FileStoreService;
+import cn.reghao.oss.store.service.StoreLocalService;
+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.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2022-03-23 09:22:01
+ */
+@Slf4j
+@Component
+public class SpringLifecycle implements ApplicationRunner, DisposableBean {
+    private final SpringProperties springProperties;
+    private final FileStoreService fileStoreService;
+    private final StoreLocalService storeLocalService;
+
+    public SpringLifecycle(SpringProperties springProperties, DubboProperties dubboProperties, FileStoreService fileStoreService,
+                           StoreLocalService storeLocalService) {
+        this.springProperties = springProperties;
+        this.fileStoreService = fileStoreService;
+        this.storeLocalService = storeLocalService;
+    }
+
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        initStoreNode();
+        log.info("执行文件任务...");
+        log.info("文件任务执行完成...");
+    }
+
+    private void initStoreNode() throws Exception {
+        // 注册 oss-store 到 oss-console
+        String nodeAddr = springProperties.getNodeAddress();
+        int httpPort = springProperties.getHttpPort();
+        int rpcPort = springProperties.getRpcPort();
+        List<StoreDiskDto> list = fileStoreService.getStoreDisks().stream()
+                .map(StoreDiskDto::new)
+                .collect(Collectors.toList());
+        StoreNodeDto storeNodeDto = new StoreNodeDto(nodeAddr, httpPort, rpcPort, list);
+        storeLocalService.register(storeNodeDto);
+        log.info("StoreNode 已注册到 oss-console...");
+    }
+
+    @Override
+    public void destroy() {
+    }
+}

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

@@ -0,0 +1,62 @@
+package cn.reghao.oss.store.config.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/config/exception/FilterExceptionHandler.java

@@ -0,0 +1,43 @@
+package cn.reghao.oss.store.config.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);
+    }
+}

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

@@ -0,0 +1,74 @@
+package cn.reghao.oss.store.config.inerceptor;
+
+import cn.reghao.jutil.web.ServletUtil;
+import cn.reghao.oss.api.dto.NodeProperties;
+import cn.reghao.oss.store.service.StoreLocalService;
+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;
+
+/**
+ * 访问日志拦截器
+ *
+ * @author reghao
+ * @date 2021-12-30 12:19:07
+ */
+@Slf4j
+@Component
+public class AccessLogInterceptor implements HandlerInterceptor {
+    private final StoreLocalService storeLocalService;
+
+    public AccessLogInterceptor(StoreLocalService storeLocalService) {
+        this.storeLocalService = storeLocalService;
+    }
+
+    @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 host = ServletUtil.getHeader("host");
+        String referFrom = null;
+        NodeProperties nodeProperties = storeLocalService.getNodeProperties(host);
+        if (nodeProperties != null) {
+            referFrom = nodeProperties.getReferer();
+        }
+
+        String objectName = uri.replaceFirst("/", "");
+        if (objectName.startsWith("img/")) {
+            return true;
+        } else if (referer == null || (referFrom != 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 {
+    }
+}

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

@@ -0,0 +1,50 @@
+package cn.reghao.oss.store.config.inerceptor;
+
+import cn.reghao.oss.api.util.AuthContext;
+import cn.reghao.oss.api.util.JwtUtil;
+import cn.reghao.jutil.web.ServletUtil;
+import cn.reghao.oss.api.dto.OssPayload;
+import com.github.benmanes.caffeine.cache.Cache;
+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 Cache<String, String> cache;
+
+    public TokenFilter(Cache<String, String> cache) {
+        this.cache = cache;
+    }
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException {
+        int userId = -1;
+        String token = ServletUtil.getBearerToken(request);
+        if (token != null) {
+            String secretKey = cache.getIfPresent(token);
+            if (secretKey != null) {
+                OssPayload ossPayload = JwtUtil.getOssPayload(token, secretKey);
+                userId = ossPayload.getUserId();
+            }
+        }
+
+        try (AuthContext context = new AuthContext(userId)) {
+            chain.doFilter(request, response);
+        }
+    }
+
+    @Override
+    public void destroy() {
+    }
+}

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

@@ -0,0 +1,68 @@
+package cn.reghao.oss.store.config.mybatis;
+
+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/mybatis/PageListInterceptor.java

@@ -0,0 +1,83 @@
+package cn.reghao.oss.store.config.mybatis;
+
+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");
+    }
+}

+ 25 - 0
oss-store/src/main/java/cn/reghao/oss/store/config/props/DubboProperties.java

@@ -0,0 +1,25 @@
+package cn.reghao.oss.store.config.props;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 12:54:20
+ */
+@Getter
+@Setter
+@Component
+@ConfigurationProperties(prefix = "dubbo")
+public class DubboProperties {
+    private Protocol protocol;
+
+    @Setter
+    @Getter
+    static class Protocol {
+        private String name;
+        private int port;
+    }
+}

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

@@ -0,0 +1,22 @@
+package cn.reghao.oss.store.config.props;
+
+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 storeHost;
+    private List<String> diskDirs;
+    private String consoleHost;
+}

+ 34 - 0
oss-store/src/main/java/cn/reghao/oss/store/config/props/SpringProperties.java

@@ -0,0 +1,34 @@
+package cn.reghao.oss.store.config.props;
+
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 12:58:00
+ */
+@Component
+public class SpringProperties {
+    private final ServerProperties serverProperties;
+    private final DubboProperties dubboProperties;
+    private final OssProperties ossProperties;
+
+    public SpringProperties(ServerProperties serverProperties, DubboProperties dubboProperties,
+                            OssProperties ossProperties) {
+        this.serverProperties = serverProperties;
+        this.dubboProperties = dubboProperties;
+        this.ossProperties = ossProperties;
+    }
+
+    public String getNodeAddress() {
+        return ossProperties.getStoreHost();
+    }
+
+    public int getHttpPort() {
+        return serverProperties.getPort();
+    }
+
+    public int getRpcPort() {
+        return dubboProperties.getProtocol().getPort();
+    }
+}

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

@@ -1,7 +1,7 @@
 package cn.reghao.oss.store.config.web;
 
-import cn.reghao.oss.store.inerceptor.AccessLogInterceptor;
-import cn.reghao.oss.store.inerceptor.TokenFilter;
+import cn.reghao.oss.store.config.inerceptor.AccessLogInterceptor;
+import cn.reghao.oss.store.config.inerceptor.TokenFilter;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;

+ 45 - 36
oss-store/src/main/java/cn/reghao/oss/store/controller/ObjectGetController.java

@@ -1,20 +1,22 @@
 package cn.reghao.oss.store.controller;
 
+import cn.reghao.jutil.web.ServletUtil;
 import cn.reghao.oss.api.constant.ChannelAction;
-import cn.reghao.oss.store.config.OssProperties;
+import cn.reghao.oss.api.constant.ObjectScope;
+import cn.reghao.oss.api.dto.ObjectChannel;
+import cn.reghao.oss.api.dto.ObjectMeta;
+import cn.reghao.oss.store.db.repository.ObjectRepository;
 import cn.reghao.oss.store.service.GetObjectService;
-import cn.reghao.oss.store.util.JwtUtil;
+import cn.reghao.oss.store.service.StoreLocalService;
+import cn.reghao.oss.api.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
@@ -23,13 +25,16 @@ import java.util.concurrent.TimeUnit;
 @RestController
 public class ObjectGetController {
     private final GetObjectService getObjectService;
+    private final ObjectRepository objectRepository;
+    private final StoreLocalService storeLocalService;
     private final Cache<String, String> cache;
-    private final OssProperties ossProperties;
 
-    public ObjectGetController(GetObjectService getObjectService, OssProperties ossProperties) {
+    public ObjectGetController(GetObjectService getObjectService, ObjectRepository objectRepository, 
+                               StoreLocalService storeLocalService, Cache<String, String> cache) {
         this.getObjectService = getObjectService;
-        this.cache = Caffeine.newBuilder().maximumSize(10_000).expireAfterAccess(1, TimeUnit.HOURS).build();
-        this.ossProperties = ossProperties;
+        this.objectRepository = objectRepository;
+        this.storeLocalService = storeLocalService;
+        this.cache = cache;
     }
 
     @RequestMapping(value = "/**", method = RequestMethod.HEAD)
@@ -49,58 +54,62 @@ public class ObjectGetController {
                           @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);
+        ObjectMeta objectMeta = objectRepository.getObjectMetaByName(objectName);
+        if (objectMeta == null) {
+            getObjectService.writeResponse(HttpServletResponse.SC_NOT_FOUND);
             return;
-        }
-
-        if (objectName.startsWith(UploadChannel.avatar.getPrefix())
-                || objectName.startsWith(UploadChannel.image.getPrefix())
-                || objectName.startsWith(UploadChannel.img.getPrefix())) {
-            getObjectService.getObject(objectName);
+        } else if (client != null && !client.isBlank()) {
+            getObjectService.getObject(objectMeta);
+            return;
+        } else if (objectMeta.getScope() == ObjectScope.PUBLIC.getCode()) {
+            getObjectService.getObject(objectMeta);
             return;
         }
 
+        String secretKey = cache.getIfPresent(token);
         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 host = ServletUtil.getHeader("host");
+        String url = String.format("//%s/%s", host, objectName);
         String requestString = String.format("%s%s?%s", "GET", url, queryString);
-        boolean valid = SignatureUtil.valid(requestString, ossProperties.getSecretKey(), sign);
+        boolean valid = SignatureUtil.valid(requestString, secretKey, sign);
         if (!valid) {
-            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN);
+            String payload = "sign invalid";
+            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN, payload);
             return;
         }
 
         long current = System.currentTimeMillis();
         if (current > timestamp) {
-            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN);
+            String payload = "timestamp invalid";
+            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN, payload);
             return;
         }
 
-        /*String value = cache.getIfPresent(nonce);
-        if (value == null) {
-            cache.put(nonce, nonce);
-        } else {
-            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN);
+        OssPayload ossPayload = JwtUtil.getOssPayload(token, secretKey);
+        int loginUser = ossPayload.getUserId();
+        int channelId = ossPayload.getChannelId();
+        ObjectChannel objectChannel = storeLocalService.getChannelById(loginUser, channelId);
+        if (objectChannel == null) {
+            String payload = String.format("channel_id %s not exist", channelId);
+            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN, payload);
             return;
-        }*/
+        }
 
-        OssPayload ossPayload = JwtUtil.getOssPayload(token, ossProperties.getSecretKey());
-        int channelId = ossPayload.getChannelId();
-        long userId = ossPayload.getUserId();
-        String prefix = UploadChannel.getUploadChannel(channelId).getPrefix();
+        String prefix = objectChannel.getPrefix();
         if (!objectName.startsWith(prefix)) {
-            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN);
+            String payload = String.format("channel prefix %s not matched", prefix);
+            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN, payload);
             return;
         }
 
         String action = ossPayload.getAction();
         if (ChannelAction.access.getName().equals(action)) {
-            getObjectService.getObject(objectName);
+            getObjectService.getObject(objectMeta);
         } else if (ChannelAction.download.getName().equals(action)) {
-            getObjectService.downloadObject(objectName);
+            getObjectService.writeDownloadContent(objectMeta);
         } else {
-            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN);
+            String payload = String.format("channel action %s not matched", action);
+            getObjectService.writeResponse(HttpServletResponse.SC_FORBIDDEN, payload);
         }
     }
 }

+ 51 - 50
oss-store/src/main/java/cn/reghao/oss/store/controller/ObjectUploadController.java

@@ -1,21 +1,18 @@
 package cn.reghao.oss.store.controller;
 
-import cn.reghao.oss.store.config.OssProperties;
+import cn.reghao.oss.api.dto.ObjectChannel;
+import cn.reghao.oss.api.util.AuthContext;
 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.oss.store.service.*;
+import cn.reghao.oss.api.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 com.github.benmanes.caffeine.cache.Cache;
 import org.apache.commons.io.FileUtils;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
@@ -36,18 +33,18 @@ public class ObjectUploadController {
     private final FileStoreService fileStoreService;
     private final ObjectNameService objectNameService;
     private final PutObjectService putObjectService;
-    private final FileProcessor fileProcessor;
-    private final OssProperties ossProperties;
+    private final StoreLocalService storeLocalService;
+    private final Cache<String, String> cache;
 
     public ObjectUploadController(ChannelValidateService channelValidateService, FileStoreService fileStoreService,
                                   ObjectNameService objectNameService, PutObjectService putObjectService,
-                                  FileProcessor fileProcessor, OssProperties ossProperties) {
+                                  StoreLocalService storeLocalService, Cache<String, String> cache) {
         this.channelValidateService = channelValidateService;
         this.fileStoreService = fileStoreService;
         this.objectNameService = objectNameService;
         this.putObjectService = putObjectService;
-        this.fileProcessor = fileProcessor;
-        this.ossProperties = ossProperties;
+        this.storeLocalService = storeLocalService;
+        this.cache = cache;
     }
 
     @PutMapping(value = "/**")
@@ -56,40 +53,45 @@ public class ObjectUploadController {
     }
 
     @PostMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
-    public ResponseEntity<String> postObject(MultipartFile file, Integer channelId,
-                                             String client, String sha256sum, Long userId) throws Exception {
+    public ResponseEntity<String> postObject(MultipartFile file, Integer channelId, String objectName,
+                                             String client, String sha256sum) 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);
+        String token = ServletUtil.getBearerToken();
+        if (token == null) {
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
+                    .body(WebResult.failWithMsg("no token in request"));
+        }
+
+        String secretKey = cache.getIfPresent(token);
+        OssPayload ossPayload = JwtUtil.getOssPayload(token, secretKey);
+        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"));
         }
 
+        int loginUser = ossPayload.getUserId();
+        ObjectChannel objectChannel = storeLocalService.getChannelById(loginUser, channelId);
+        if (objectChannel == null) {
+            String errMsg = String.format("channel validate failed, channel %s not exist", channelId);
+            return ResponseEntity.status(HttpStatus.FORBIDDEN)
+                    .body(WebResult.failWithMsg(errMsg));
+        }
+
+        int userId1 = ossPayload.getUserId();
+        AuthContext context = new AuthContext(userId1);
+
         /* 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);
+        Result result = channelValidateService.validateFile(savedFile, objectChannel);
         if (result.getCode() != 0) {
             FileUtils.deleteQuietly(savedFile);
             return ResponseEntity.status(HttpStatus.FORBIDDEN).body(WebResult.result(result));
@@ -106,20 +108,19 @@ public class ObjectUploadController {
             filename = "";
         }
 
-        ObjectProp objectProp = objectNameService.getObjectProp(channelId, filename);
+        ObjectProp objectProp = objectNameService.getObjectProp(objectChannel, filename);
         ObjectResult objectResult = putObjectService.putObject(objectProp, contentId, savedFile, filename, sha256sum1);
+        String objectId = objectResult.getObjectId();
+        String domain = ServletUtil.getHeader("host");
+        String objectUrl = String.format("//%s/%s", domain, objectResult.getObjectName());
         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();
+        if (objectChannel.getSeturl()) {
+            uploadFileRet = new UploadFileRet(objectId, objectUrl);
+        } else {
+            uploadFileRet = new UploadFileRet(objectId);
         }
 
-        putObjectService.deleteObject(objectResult.getObjectId());
-        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errMsg);
+        return ResponseEntity.status(HttpStatus.OK).body(WebResult.success(uploadFileRet));
     }
 
     @DeleteMapping(value = "/")

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

@@ -1,6 +1,5 @@
 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;
@@ -14,10 +13,6 @@ import java.util.List;
  */
 @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);
 }

+ 5 - 29
oss-store/src/main/java/cn/reghao/oss/store/db/mapper/FileMetaMapper.java

@@ -1,11 +1,9 @@
 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;
 
@@ -17,40 +15,18 @@ import java.util.List;
  */
 @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);
+    void updateScopeByObjectName(@Param("scope") int scope, @Param("objectName") String objectName);
 
-    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<FileInfo> findByPid(@Param("pid") String pid, @Param("pageSize") int pageSize, @Param("nextId") String nextId);
 
     /******************************************************************************************************************/
-    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();
+    List<FileMeta> findAll0(@Param("objectId") String objectId, @Param("max") Integer max, @Param("regex") String regex);
+    List<FileMeta> findAll2(@Param("objectId") String objectId, @Param("prefix") String prefix,
+                            @Param("start") String start, @Param("max") Integer max);
 }

+ 6 - 29
oss-store/src/main/java/cn/reghao/oss/store/db/repository/ObjectRepository.java

@@ -40,15 +40,10 @@ public class ObjectRepository {
         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 = "#objectName")
+    public void updateObjectScope(int scope, String objectName) {
+        fileMetaMapper.updateScopeByObjectName(scope, objectName);
+        log.info("evict {}", objectName);
     }
 
     @CacheEvict(cacheNames = "oss:store:objectMeta", key = "#fileMeta.objectName")
@@ -72,24 +67,10 @@ public class ObjectRepository {
         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);
     }
@@ -99,8 +80,8 @@ public class ObjectRepository {
     }
 
     @Cacheable(cacheNames = "oss:store:objectMeta", key = "#objectName", unless = "#result == null")
-    public ObjectMeta getObjectMeta(String objectName) {
-        log.info("cache miss");
+    public ObjectMeta getObjectMetaByName(String objectName) {
+        log.info("cache miss {}", objectName);
         ObjectMeta objectMeta = fileMetaMapper.findObjectMeta(objectName);
         return objectMeta;
     }
@@ -108,8 +89,4 @@ public class ObjectRepository {
     public ObjectMeta getObjectMetaById(String objectId) {
         return fileMetaMapper.findObjectMetaById(objectId);
     }
-
-    public List<String> getObjectNames(List<String> objectIds) {
-        return fileMetaMapper.findObjectNames(objectIds);
-    }
 }

+ 8 - 11
oss-store/src/main/java/cn/reghao/oss/store/model/po/DataBlock.java

@@ -1,7 +1,6 @@
 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;
@@ -11,27 +10,25 @@ import lombok.Setter;
  * @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;
+    //private String baseDir;
+    //private String relativeDir;
+    //private int index;
+    //private long start;
+    //private long end;
+    //private String objectId;
 
-    public DataBlock(String contentId, int index, String blockId, String absolutePath, long size) {
+    public DataBlock(String contentId, String blockId, String host, String absolutePath, long size) {
         this.contentId = contentId;
-        this.index = index;
         this.blockId = blockId;
+        this.host = host;
         this.absolutePath = absolutePath;
         this.size = size;
     }

+ 8 - 12
oss-store/src/main/java/cn/reghao/oss/store/model/po/FileMeta.java

@@ -1,6 +1,6 @@
 package cn.reghao.oss.store.model.po;
 
-import cn.reghao.oss.store.util.UserContext;
+import cn.reghao.oss.api.util.AuthContext;
 import cn.reghao.jutil.jdk.db.BaseObject;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
@@ -25,9 +25,8 @@ public class FileMeta extends BaseObject<Integer> {
     private Long size;
     private Integer fileType;
     private String contentType;
-    private int scope;
-    private Long uploadBy;
-    private Boolean diskFile;
+    private Integer scope;
+    private Integer uploadBy;
 
     // 目录对象
     public FileMeta(String objectName, String objectId, String filename, String pid, int scope) {
@@ -40,13 +39,12 @@ public class FileMeta extends BaseObject<Integer> {
         this.contentType = "0";
         this.sha256sum = "0";
         this.pid = pid;
-        this.uploadBy = UserContext.getUser();
-        this.diskFile = false;
+        this.uploadBy = AuthContext.getUserId();
         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) {
+                    int fileType, String contentType, String sha256sum, String pid, int scope) {
         this.objectName = objectName;
         this.objectId = objectId;
         this.contentId = contentId;
@@ -56,12 +54,11 @@ public class FileMeta extends BaseObject<Integer> {
         this.contentType = contentType;
         this.sha256sum = sha256sum;
         this.pid = pid;
-        this.uploadBy = UserContext.getUser();
-        this.diskFile = diskFile;
+        this.uploadBy = AuthContext.getUserId();
         this.scope = scope;
     }
 
-    public FileMeta(String objectName, String objectId, String filename, FileMeta fileMeta, boolean diskFile, int scope) {
+    public FileMeta(String objectName, String objectId, String filename, FileMeta fileMeta, int scope) {
         this.objectName = objectName;
         this.objectId = objectId;
         this.contentId = fileMeta.getContentId();
@@ -71,8 +68,7 @@ public class FileMeta extends BaseObject<Integer> {
         this.contentType = fileMeta.getContentType();
         this.sha256sum = fileMeta.getSha256sum();
         this.pid = fileMeta.getPid();
-        this.uploadBy = UserContext.getUser();
-        this.diskFile = diskFile;
+        this.uploadBy = AuthContext.getUserId();
         this.scope = scope;
     }
 }

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

@@ -11,7 +11,6 @@ import lombok.Getter;
 @Getter
 public class ObjectProp {
     private String objectName;
-    private boolean diskFile;
     private int scope;
     private String pid;
 }

+ 59 - 0
oss-store/src/main/java/cn/reghao/oss/store/rpc/DiskServiceImpl.java

@@ -0,0 +1,59 @@
+package cn.reghao.oss.store.rpc;
+
+import cn.reghao.oss.api.dto.FileInfo;
+import cn.reghao.oss.api.iface.DiskService;
+import cn.reghao.oss.store.db.mapper.FileMetaMapper;
+import cn.reghao.oss.store.model.po.FileMeta;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2024-07-11 16:46:50
+ */
+@DubboService
+@Service
+public class DiskServiceImpl implements DiskService {
+    private final FileMetaMapper fileMetaMapper;
+
+    public DiskServiceImpl(FileMetaMapper fileMetaMapper) {
+        this.fileMetaMapper = fileMetaMapper;
+    }
+
+    public List<FileInfo> getFiles(String pid, int pageSize, String nextObjectId) {
+        return fileMetaMapper.findByPid(pid, pageSize, nextObjectId);
+    }
+
+    public List<FileInfo> getFiles(int pageNumber, int pageSize) {
+        String bucket = "";
+        String prefix = "image/i/";
+        String startAfter = "image/cover/";
+        startAfter = "";
+        Integer maxKeys = 10;
+
+        StringBuilder regex = new StringBuilder();
+        //regex.append("^").append(prefix).append("([^/])+/?$");
+        regex.append(prefix);
+        List<FileMeta> list;
+        if (startAfter.isBlank()) {
+            list = fileMetaMapper.findAll0(bucket, maxKeys, regex.toString());
+        } else {
+            list = fileMetaMapper.findAll2(bucket, prefix, startAfter, maxKeys);
+        }
+
+        return list.stream().map(fileMeta -> {
+            String objectId = fileMeta.getObjectId();
+            String objectName = fileMeta.getObjectName();
+            int fileType = fileMeta.getFileType();
+            String filename = fileMeta.getFilename();
+            long size = fileMeta.getSize();
+            LocalDateTime updateTime = fileMeta.getUpdateTime();
+            return new FileInfo();
+        }).collect(Collectors.toList());
+    }
+}

+ 167 - 0
oss-store/src/main/java/cn/reghao/oss/store/rpc/StoreServiceImpl.java

@@ -0,0 +1,167 @@
+package cn.reghao.oss.store.rpc;
+
+import cn.reghao.jutil.jdk.security.RandomString;
+import cn.reghao.oss.api.constant.ChannelAction;
+import cn.reghao.oss.api.constant.ObjectScope;
+import cn.reghao.oss.api.dto.*;
+import cn.reghao.oss.api.dto.media.AudioInfo;
+import cn.reghao.oss.api.dto.media.ConvertedImageInfo;
+import cn.reghao.oss.api.dto.media.ImageInfo;
+import cn.reghao.oss.api.dto.media.VideoInfo;
+import cn.reghao.oss.api.util.JwtUtil;
+import cn.reghao.oss.store.db.repository.ObjectRepository;
+import cn.reghao.oss.store.model.po.FileMeta;
+import cn.reghao.oss.store.service.FileStoreService;
+import cn.reghao.oss.store.service.ObjectNameService;
+import cn.reghao.oss.api.iface.StoreService;
+import cn.reghao.oss.store.service.SignService;
+import cn.reghao.oss.store.task.MediaFileProcessor;
+import com.github.benmanes.caffeine.cache.Cache;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 获取存储节点信息
+ * oss-console 调用
+ *
+ * @author reghao
+ * @date 2023-08-01 14:54:23
+ */
+@DubboService
+@Service
+public class StoreServiceImpl implements StoreService {
+    private final LocalDateTime epoch = LocalDateTime.parse("1970-01-01T08:00");
+
+    private final ObjectNameService objectNameService;
+    private final FileStoreService fileStoreService;
+    private final ObjectRepository objectRepository;
+    private final SignService signService;
+    private final MediaFileProcessor mediaFileProcessor;
+    private final Cache<String, String> cache;
+
+    public StoreServiceImpl(ObjectNameService objectNameService, FileStoreService fileStoreService,
+                            ObjectRepository objectRepository, SignService signService,
+                            MediaFileProcessor mediaFileProcessor, Cache<String, String> cache) {
+        this.objectNameService = objectNameService;
+        this.fileStoreService = fileStoreService;
+        this.objectRepository = objectRepository;
+        this.signService = signService;
+        this.mediaFileProcessor = mediaFileProcessor;
+        this.cache = cache;
+    }
+
+    @Override
+    public void createChannel(String channelPrefix) {
+        objectNameService.createParentDirs(channelPrefix, ObjectScope.PUBLIC.getCode());
+    }
+
+    @Override
+    public StoreInfo getStoreInfo() {
+        List<StoreDiskDto> diskDtos = fileStoreService.getStoreDisks().stream()
+                .map(StoreDiskDto::new)
+                .collect(Collectors.toList());
+        return new StoreInfo(diskDtos);
+    }
+
+    @Override
+    public String getUploadToken(int channelId, int loginUser, int expire) {
+        String secretKey = RandomString.getString(128);
+        String action = ChannelAction.upload.getName();
+        long expireAt = System.currentTimeMillis() + expire*1000L;
+        OssPayload ossPayload = new OssPayload(action, channelId, loginUser);
+        String uploadToken = JwtUtil.createToken(ossPayload, expireAt, secretKey);
+        cache.put(uploadToken, secretKey);
+        return uploadToken;
+    }
+
+    @Override
+    public void setObjectScope(String objectId, int scope) {
+        FileMeta fileMeta = objectRepository.getByObjectId(objectId);
+        objectRepository.updateObjectScope(scope, fileMeta.getObjectName());
+    }
+
+    @Override
+    public void deleteByObjectId(String objectId) {
+        FileMeta fileMeta = objectRepository.getByObjectId(objectId);
+        objectRepository.deleteObject(fileMeta);
+    }
+
+    @Override
+    public void deleteByObjectUrl(String objectUrl) {
+        String objectName = "";
+        FileMeta fileMeta = objectRepository.getByObjectName(objectName);
+        objectRepository.deleteObject(fileMeta);
+    }
+
+    @Override
+    public ObjectInfo getObjectInfo(String objectId) {
+        FileMeta fileMeta = objectRepository.getByObjectId(objectId);
+        if (fileMeta == null) {
+            return null;
+        }
+
+        String objectName = fileMeta.getObjectName();
+        int fileType = fileMeta.getFileType();
+        String filename = fileMeta.getFilename();
+        long size = fileMeta.getSize();
+        return new ObjectInfo(objectId, objectName, fileType, filename, size);
+    }
+
+    @Override
+    public String getSignedUrl(String domain, int loginUser, String objectId, int expire) {
+        ObjectMeta objectMeta = objectRepository.getObjectMetaById(objectId);
+        String url = String.format("//%s/%s", domain, objectMeta.getObjectName());
+        return signService.getSignedUrl(loginUser, url, expire);
+    }
+
+    @Override
+    public VideoInfo getVideoInfo(String objectId) {
+        try {
+            VideoInfo videoInfo = mediaFileProcessor.getVideoInfo(objectId);
+            return videoInfo;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    @Override
+    public ImageInfo getImageInfo(String objectId) {
+        try {
+            ImageInfo imageInfo = mediaFileProcessor.getImageInfo(objectId);
+            return imageInfo;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    @Override
+    public ConvertedImageInfo getWebpInfo(String objectId) {
+        try {
+            return mediaFileProcessor.getWebpInfo(objectId);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    @Override
+    public AudioInfo getAudioInfo(String objectId) {
+        try {
+            AudioInfo audioInfo = mediaFileProcessor.getAudioInfo(objectId);
+            return audioInfo;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+}

+ 12 - 51
oss-store/src/main/java/cn/reghao/oss/store/service/ChannelValidateService.java

@@ -1,9 +1,9 @@
 package cn.reghao.oss.store.service;
 
+import cn.reghao.oss.api.constant.ObjectType;
+import cn.reghao.oss.api.dto.ObjectChannel;
 import cn.reghao.oss.store.util.FileType;
 import cn.reghao.jutil.jdk.result.Result;
-import cn.reghao.oss.api.constant.SupportedMedia;
-import cn.reghao.oss.api.constant.UploadChannel;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
@@ -16,64 +16,25 @@ import java.io.File;
 @Slf4j
 @Service
 public class ChannelValidateService {
-    public Result validate(File file, int channelId) {
-        UploadChannel channel = UploadChannel.getUploadChannel(channelId);
-        switch (channel) {
-            case disk:
-                return Result.success();
-            case video:
-                return validateVideo(file);
-            case audio:
-                return validateAudio(file);
-            case avatar:
-                return validateImage(file, UploadChannel.avatar.getMaxSize());
-            case image:
-            case img:
-                return validateImage(file, UploadChannel.image.getMaxSize());
-            case photo:
-                return validateImage(file, UploadChannel.photo.getMaxSize());
-            default:
-                String errMsg = String.format("%s 的文件暂时无法处理", channel.getPrefix());
-                return Result.fail(errMsg);
-        }
-    }
-
-    private Result validateVideo(File file) {
-        String mediaType = FileType.getMediaType(file.getAbsolutePath());
-        long maxSize = UploadChannel.video.getMaxSize();
-        long len = file.length();
-        if (len > maxSize) {
-            String errMsg = String.format("视频文件大小不能超过 %s bytes", maxSize);
+    public Result validateFile(File file, ObjectChannel objectChannel) {
+        int fileType = objectChannel.getFileType();
+        ObjectType objectType = ObjectType.getByCode(fileType);
+        if (objectType == null) {
+            String errMsg = String.format("channel validate failed, file_type %s not exist", fileType);
             return Result.fail(errMsg);
-        } else if (!mediaType.startsWith("video")) {
-            return Result.fail("非视频文件格式");
         }
 
-        return Result.success();
-    }
-
-    private Result validateAudio(File file) {
-        String mediaType = FileType.getMediaType(file.getAbsolutePath());
-        long maxSize = UploadChannel.audio.getMaxSize();
+        long maxSize = objectChannel.getMaxSize();
         long len = file.length();
         if (len > maxSize) {
-            String errMsg = String.format("音频文件大小不能超过 %s bytes", maxSize);
+            String errMsg = String.format("channel validate failed, the size of %s file bigger than %s bytes", objectType.name(), maxSize);
             return Result.fail(errMsg);
-        } else if (!mediaType.startsWith("audio")) {
-            return Result.fail("非音频文件格式");
         }
 
-        return Result.success();
-    }
-
-    private Result validateImage(File file, long maxSize) {
+        String contentType = ObjectType.getDescByCode(fileType);
         String mediaType = FileType.getMediaType(file.getAbsolutePath());
-        long len = file.length();
-        if (len > maxSize) {
-            String errMsg = String.format("图片文件大小不能超过 %s bytes", maxSize);
-            return Result.fail(errMsg);
-        } else if (!SupportedMedia.imageFormats.contains(mediaType.replace("image/", ""))) {
-            String errMsg = String.format("系统仅支持 %s 等图片格式", SupportedMedia.imageFormats);
+        if (!contentType.equals(ObjectType.Other.getDesc()) && !mediaType.startsWith(contentType)) {
+            String errMsg = String.format("channel validate failed, the format of file is not %s", contentType);
             return Result.fail(errMsg);
         }
 

+ 45 - 3
oss-store/src/main/java/cn/reghao/oss/store/service/FileStoreService.java

@@ -1,16 +1,21 @@
 package cn.reghao.oss.store.service;
 
 import cn.reghao.jutil.jdk.store.LoadBalancer;
+import cn.reghao.jutil.jdk.store.LocalStore;
+import cn.reghao.jutil.jdk.store.LocalStores;
 import cn.reghao.jutil.jdk.store.StoreDir;
+import cn.reghao.oss.store.config.props.OssProperties;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.io.FileUtils;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.PostConstruct;
 import java.io.*;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
-import java.util.EnumSet;
+import java.util.*;
 
 import static java.nio.file.StandardOpenOption.*;
 
@@ -18,12 +23,49 @@ import static java.nio.file.StandardOpenOption.*;
  * @author reghao
  * @date 2022-04-26 15:09:06
  */
+@Slf4j
 @Service
 public class FileStoreService {
     private final LoadBalancer loadBalancer;
+    private final OssProperties ossProperties;
 
-    public FileStoreService() {
+    public FileStoreService(OssProperties ossProperties) {
         this.loadBalancer = new LoadBalancer();
+        this.ossProperties = ossProperties;
+    }
+
+    @PostConstruct
+    public 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) {
+            Map<String, Integer> map = new HashMap<>();
+            List<SubDirCount> list = dataBlockMapper.findSubDirCount("");
+            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("本地磁盘数据初始化完成...");
+    }
+
+    // TODO 返回实时磁盘容量
+    public List<LocalStore> getStoreDisks() {
+        return LocalStores.getLocalStores();
     }
 
     public String genFilePath(String contentId, long size, String suffix) {
@@ -106,7 +148,7 @@ public class FileStoreService {
         }
 
         FileOutputStream fos = new FileOutputStream(file);
-        // 1MiB
+        // 1MB
         int len = 1024*1024;
         byte[] buf = new byte[len];
         int readLen;

+ 27 - 19
oss-store/src/main/java/cn/reghao/oss/store/service/GetObjectService.java

@@ -1,8 +1,7 @@
 package cn.reghao.oss.store.service;
 
-import cn.reghao.oss.store.config.OssProperties;
+import cn.reghao.oss.api.dto.NodeProperties;
 import cn.reghao.oss.store.db.mapper.FileMetaMapper;
-import cn.reghao.oss.store.db.repository.ObjectRepository;
 import cn.reghao.oss.store.model.dto.ContentRange;
 import cn.reghao.oss.store.model.po.FileMeta;
 import cn.reghao.oss.api.dto.ObjectMeta;
@@ -24,15 +23,13 @@ import java.nio.charset.StandardCharsets;
 @Service
 public class GetObjectService {
     private final FileMetaMapper fileMetaMapper;
-    // 1MiB
+    // 1MB
     private final int bufSize = 1024*1024;
-    private final ObjectRepository objectRepository;
-    private final String domain;
+    private final StoreLocalService storeLocalService;
 
-    public GetObjectService(FileMetaMapper fileMetaMapper, ObjectRepository objectRepository, OssProperties ossProperties) {
+    public GetObjectService(FileMetaMapper fileMetaMapper, StoreLocalService storeLocalService) {
         this.fileMetaMapper = fileMetaMapper;
-        this.objectRepository = objectRepository;
-        this.domain = ossProperties.getDomain();
+        this.storeLocalService = storeLocalService;
     }
     
     public void headObject(String objectName) throws IOException {
@@ -75,21 +72,20 @@ public class GetObjectService {
         outputStream.close();
     }
 
-    public void getObject(String objectName) throws IOException {
+    public void getObject(ObjectMeta objectMeta) throws IOException {
         String host = ServletUtil.getRequest().getHeader("host");
-
-        ObjectMeta objectMeta = objectRepository.getObjectMeta(objectName);
-        if (objectMeta == null) {
-            writeResponse(HttpServletResponse.SC_NOT_FOUND);
-            return;
-        }
-
         long len = objectMeta.getSize();
         String range = ServletUtil.getRequest().getHeader("range");
         if (range != null) {
             ContentRange contentRange = parseContentRange(range, len);
             writeContentRange(objectMeta, contentRange);
         } else {
+            String domain = host;
+            NodeProperties nodeProperties = storeLocalService.getNodeProperties(host);
+            if (nodeProperties != null) {
+                domain = nodeProperties.getDomain();
+            }
+
             if (host.contains(domain)) {
                 writeWholeContent(objectMeta);
             } else {
@@ -98,7 +94,7 @@ public class GetObjectService {
         }
     }
 
-    public void downloadObject(String objectName) throws IOException {
+    /*public void downloadObject(String objectName) throws IOException {
         ObjectMeta objectMeta = objectRepository.getObjectMeta(objectName);
         if (objectMeta == null) {
             writeResponse(HttpServletResponse.SC_NOT_FOUND);
@@ -106,7 +102,7 @@ public class GetObjectService {
         }
 
         writeDownloadContent(objectMeta);
-    }
+    }*/
 
     public void writeResponse(int statusCode) throws IOException {
         HttpServletResponse response = ServletUtil.getResponse();
@@ -116,6 +112,18 @@ public class GetObjectService {
         outputStream.close();
     }
 
+    public void writeResponse(int statusCode, String payload) throws IOException {
+        HttpServletResponse response = ServletUtil.getResponse();
+        response.setStatus(statusCode);
+        response.setContentType("text/html");
+        response.setHeader("Content-Length", ""+payload.length());
+
+        OutputStream outputStream = response.getOutputStream();
+        outputStream.write(payload.getBytes(StandardCharsets.UTF_8));
+        outputStream.flush();
+        outputStream.close();
+    }
+
     private ContentRange parseContentRange(String range, long len) {
         String rangeStr = StringUtils.trimAllWhitespace(range);
         String[] arr = rangeStr.replace("bytes=", "").split("-");
@@ -147,7 +155,7 @@ public class GetObjectService {
         writeResponse(outputStream, absolutePath, start, end);
     }
 
-    private void writeDownloadContent(ObjectMeta objectMeta) throws IOException {
+    public void writeDownloadContent(ObjectMeta objectMeta) throws IOException {
         HttpServletResponse response = ServletUtil.getResponse();
         response.setStatus(HttpServletResponse.SC_OK);
         response.setContentType(objectMeta.getContentType());

+ 14 - 12
oss-store/src/main/java/cn/reghao/oss/store/service/ObjectMultipartUploadService.java

@@ -1,6 +1,7 @@
 package cn.reghao.oss.store.service;
 
-import cn.reghao.oss.store.config.OssProperties;
+import cn.reghao.jutil.web.ServletUtil;
+import cn.reghao.oss.api.dto.ObjectChannel;
 import cn.reghao.oss.store.db.mapper.FileMetaMapper;
 import cn.reghao.oss.store.model.dto.PathUrl;
 import cn.reghao.oss.store.model.vo.ObjectProp;
@@ -9,7 +10,6 @@ import cn.reghao.oss.api.rest.UploadFilePart;
 import cn.reghao.oss.api.rest.UploadPrepare;
 import cn.reghao.oss.api.rest.UploadPrepareRet;
 import cn.reghao.oss.api.rest.UploadFileRet;
-import cn.reghao.oss.store.model.po.*;
 import cn.reghao.jutil.jdk.security.DigestUtil;
 import cn.reghao.oss.store.model.po.FileMeta;
 import lombok.extern.slf4j.Slf4j;
@@ -26,7 +26,7 @@ import java.util.*;
 @Slf4j
 @Service
 public class ObjectMultipartUploadService {
-    // 20MiB
+    // 20MB
     private final static long PART_SIZE = 1024*1024*20;
     private final FileMetaMapper fileMetaMapper;
     private final FileStoreService fileStoreService;
@@ -34,16 +34,13 @@ public class ObjectMultipartUploadService {
     private final Map<String, PathUrl> pathMap = new HashMap<>();
     private final ObjectNameService objectNameService;
     private final PutObjectService putObjectService;
-    private final String domain;
 
     public ObjectMultipartUploadService(FileMetaMapper fileMetaMapper, FileStoreService fileStoreService,
-                                        ObjectNameService objectNameService, PutObjectService putObjectService,
-                                        OssProperties ossProperties) {
+                                        ObjectNameService objectNameService, PutObjectService putObjectService) {
         this.fileMetaMapper = fileMetaMapper;
         this.fileStoreService = fileStoreService;
         this.objectNameService = objectNameService;
         this.putObjectService = putObjectService;
-        this.domain = ossProperties.getDomain();
     }
 
     public synchronized UploadPrepareRet prepareUpload(UploadPrepare uploadPrepare) {
@@ -53,7 +50,8 @@ public class ObjectMultipartUploadService {
             return new UploadPrepareRet("uploadId", PART_SIZE, false, null);
         } else {
             String objectName = fileMeta.getObjectName();
-            String url = String.format("https://%s/%s", domain, objectName);
+            String host = ServletUtil.getHeader("host");
+            String url = String.format("//%s/%s", host, objectName);
             return new UploadPrepareRet("uploadId", PART_SIZE, false, url);
         }
     }
@@ -80,10 +78,12 @@ public class ObjectMultipartUploadService {
         if (fileMeta != null) {
             String objectName = fileMeta.getObjectName();
             String suffix = StringUtil.getSuffix(objectName);
-            ObjectProp objectProp = objectNameService.getObjectProp(uploadFilePart.getChannelId(), suffix);
+            ObjectChannel objectChannel = null;
+            ObjectProp objectProp = objectNameService.getObjectProp(objectChannel, suffix);
             putObjectService.copyObject(objectProp, filename, fileMeta);
 
-            String url = String.format("https://%s/%s", domain, objectProp);
+            String host = ServletUtil.getHeader("host");
+            String url = String.format("//%s/%s", host, objectProp);
             return new UploadFileRet(sha256sum, url);
         }
 
@@ -118,13 +118,15 @@ public class ObjectMultipartUploadService {
 
             int channelId = uploadFilePart.getChannelId();
             String suffix = StringUtil.getSuffix(filename);
-            ObjectProp objectProp = objectNameService.getObjectProp(channelId, suffix);
+            ObjectChannel objectChannel = null;
+            ObjectProp objectProp = objectNameService.getObjectProp(objectChannel, suffix);
             File savedFile = new File(absolutePath);
             putObjectService.putObject(objectProp, contentId, savedFile, filename, absolutePath);
 
             map.remove(sha256sum);
             pathMap.remove(sha256sum);
-            String url = String.format("https://%s/%s", domain, objectProp);
+            String host = ServletUtil.getHeader("host");
+            String url = String.format("//%s/%s", host, objectProp);
             return new UploadFileRet(sha256sum, url);
         }
     }

+ 7 - 32
oss-store/src/main/java/cn/reghao/oss/store/service/ObjectNameService.java

@@ -1,12 +1,10 @@
 package cn.reghao.oss.store.service;
 
-import cn.reghao.oss.store.config.OssProperties;
+import cn.reghao.oss.api.dto.ObjectChannel;
 import cn.reghao.oss.store.db.repository.ObjectRepository;
 import cn.reghao.oss.store.model.po.FileMeta;
 import cn.reghao.oss.store.model.vo.ObjectProp;
 import cn.reghao.oss.store.util.StringUtil;
-import cn.reghao.oss.api.constant.ObjectScope;
-import cn.reghao.oss.api.constant.UploadChannel;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
@@ -20,36 +18,16 @@ import java.util.UUID;
 @Service
 public class ObjectNameService {
     private final ObjectRepository objectRepository;
-    private final String domain;
 
-    public ObjectNameService(ObjectRepository objectRepository, OssProperties ossProperties) {
+    public ObjectNameService(ObjectRepository objectRepository) {
         this.objectRepository = objectRepository;
-        this.domain = ossProperties.getDomain();
     }
 
-    public String getObjectUrl(String objectName) {
-        return String.format("//%s/%s", domain, objectName);
-    }
-
-    public String getObjectNameFromUrl(String url) {
-        return url.replace(String.format("//%s/", domain), "");
-    }
-
-    public ObjectProp getObjectProp(int channelId, String filename) throws Exception {
+    public ObjectProp getObjectProp(ObjectChannel channel, String filename) throws Exception {
+        int scope = channel.getScope();
         String suffix = StringUtil.getSuffix(filename);
-        UploadChannel channel = UploadChannel.getUploadChannel(channelId);
-        if (channel == null) {
-            throw new Exception("channelId 不合法");
-        }
-
         String objectPrefix = channel.getPrefix();
         String objectName = objectPrefix + UUID.randomUUID().toString().replace("-", "") + suffix;
-        boolean diskFile = channelId == UploadChannel.disk.getCode();
-        int scope = ObjectScope.PRIVATE.getCode();
-        if (channelId == UploadChannel.avatar.getCode() || channelId == UploadChannel.image.getCode()) {
-            scope = ObjectScope.PUBLIC.getCode();
-        }
-
         FileMeta fileMeta = objectRepository.getByObjectName(objectPrefix);
         if (fileMeta == null) {
             String errMsg = String.format("objectPrefix %s 不合法", objectPrefix);
@@ -57,21 +35,18 @@ public class ObjectNameService {
         }
 
         String pid = fileMeta.getObjectId();
-        return new ObjectProp(objectName, diskFile, scope, pid);
+        return new ObjectProp(objectName, scope, pid);
     }
 
     public ObjectProp getObjectProp(String originalObjectName, String suffix) {
         int idx = originalObjectName.lastIndexOf("/");
         String prefix = originalObjectName.substring(0, idx+1);
-        UploadChannel channel = UploadChannel.getUploadChannel(prefix);
         FileMeta fileMeta = objectRepository.getByObjectName(originalObjectName);
-        boolean diskFile = fileMeta.getDiskFile();
         int scope = fileMeta.getScope();
         String pid = fileMeta.getPid();
 
-        String objectPrefix = channel.getPrefix();
-        String objectName = objectPrefix + UUID.randomUUID().toString().replace("-", "") + suffix;
-        return new ObjectProp(objectName, diskFile, scope, pid);
+        String objectName = prefix + UUID.randomUUID().toString().replace("-", "") + suffix;
+        return new ObjectProp(objectName, scope, pid);
     }
 
     public String getObjectNameFromOriginal(String originalObjectName, String suffix) {

+ 20 - 13
oss-store/src/main/java/cn/reghao/oss/store/service/PutObjectService.java

@@ -1,5 +1,7 @@
 package cn.reghao.oss.store.service;
 
+import cn.reghao.jutil.jdk.security.DigestUtil;
+import cn.reghao.oss.store.config.props.OssProperties;
 import cn.reghao.oss.store.db.repository.ObjectRepository;
 import cn.reghao.oss.store.model.po.DataBlock;
 import cn.reghao.oss.store.model.po.FileMeta;
@@ -25,10 +27,13 @@ import java.util.UUID;
 public class PutObjectService {
     private final ObjectRepository objectRepository;
     private final ObjectNameService objectNameService;
+    private final String nodeAddress;
 
-    public PutObjectService(ObjectRepository objectRepository, ObjectNameService objectNameService) {
+    public PutObjectService(ObjectRepository objectRepository, ObjectNameService objectNameService,
+                            OssProperties ossProperties) {
         this.objectRepository = objectRepository;
         this.objectNameService = objectNameService;
+        this.nodeAddress = ossProperties.getStoreHost();
     }
 
     public ObjectResult putObject(ObjectProp objectProp, String contentId, File savedFile, String originalFilename, String sha256sum) {
@@ -44,32 +49,35 @@ public class PutObjectService {
             String objectId = UUID.randomUUID().toString().replace("-", "");
             String contentType = FileType.getMediaType(savedPath);
             int fileType = FileType.getFileType(contentType);
-
-            boolean diskFile = objectProp.isDiskFile();
             int scope = objectProp.getScope();
             fileMeta = new FileMeta(objectName, objectId, contentId, originalFilename, size,
-                    fileType, contentType, sha256sum, pid, diskFile, scope);
+                    fileType, contentType, sha256sum, pid, scope);
             String blockId = UUID.randomUUID().toString();
-            List<DataBlock> list = List.of(new DataBlock(contentId, 0, blockId, savedPath, size));
+            List<DataBlock> list = List.of(new DataBlock(contentId, blockId, nodeAddress, savedPath, size));
             objectRepository.saveObject(fileMeta, list);
             return new ObjectResult(objectName, objectId, fileType, savedPath);
         }
     }
 
+    public ObjectResult putObject(String originalObjectName, String contentId, String suffix, File savedFile) throws Exception {
+        ObjectProp objectProp = objectNameService.getObjectProp(originalObjectName, suffix);
+        String sha256sum = DigestUtil.sha256sum(savedFile.getAbsolutePath());
+        return putObject(objectProp, contentId, savedFile, "", sha256sum);
+    }
+
     public void putObject(String objectName, byte[] bytes) {
     }
 
     public ObjectResult copyObject(ObjectProp objectProp, String filename, FileMeta fileMeta) {
         String dupObjectId = fileMeta.getObjectId();
-        ObjectMeta objectMeta = objectRepository.getObjectMeta(fileMeta.getObjectName());
+        ObjectMeta objectMeta = objectRepository.getObjectMetaByName(fileMeta.getObjectName());
 
         int fileType = fileMeta.getFileType();
         String savedPath = objectMeta.getAbsolutePath();
         String objectName = objectProp.getObjectName();
         String objectId = UUID.randomUUID().toString().replace("-", "");
-        boolean diskFile = objectProp.isDiskFile();
         int scope = objectProp.getScope();
-        FileMeta fileMeta1 = new FileMeta(objectName, objectId, filename, fileMeta, diskFile, scope);
+        FileMeta fileMeta1 = new FileMeta(objectName, objectId, filename, fileMeta, scope);
         objectRepository.saveFileMeta(fileMeta1);
         return new ObjectResult(objectName, objectId, fileType, savedPath, dupObjectId);
     }
@@ -81,11 +89,10 @@ public class PutObjectService {
         String suffix = StringUtil.getSuffix(fromObjectName);
         String filename = fileMeta.getFilename();
         String savedPath = "";
-        boolean diskFile = fileMeta.getDiskFile();
         int scope = fileMeta.getScope();
         String toObjectName = objectNameService.getObjectNameFromOriginal(fromObjectName, suffix);
         String toObjectId = UUID.randomUUID().toString().replace("-", "");
-        FileMeta fileMeta1 = new FileMeta(toObjectName, toObjectId, filename, fileMeta, diskFile, scope);
+        FileMeta fileMeta1 = new FileMeta(toObjectName, toObjectId, filename, fileMeta, scope);
         objectRepository.saveFileMeta(fileMeta1);
         return new ObjectResult(toObjectName, toObjectId, fileType, savedPath);
     }
@@ -98,16 +105,16 @@ public class PutObjectService {
         String suffix = StringUtil.getSuffix(fromObjectName);
         String filename = fileMeta.getFilename();
         String savedPath = "";
-        boolean diskFile = fileMeta.getDiskFile();
         int scope = fileMeta.getScope();
         String toObjectName = objectNameService.getObjectNameFromOriginal(fromObjectName, suffix);
         String toObjectId = UUID.randomUUID().toString().replace("-", "");
-        FileMeta fileMeta1 = new FileMeta(toObjectName, toObjectId, filename, fileMeta, diskFile, scope);
+        FileMeta fileMeta1 = new FileMeta(toObjectName, toObjectId, filename, fileMeta, scope);
         objectRepository.saveFileMeta(fileMeta1);
         return new ObjectResult(toObjectName, toObjectId, fileType, savedPath);
     }
 
     public void deleteObject(String objectId) {
-        objectRepository.deleteByObjectIds(List.of(objectId));
+        FileMeta fileMeta = objectRepository.getByObjectId(objectId);
+        objectRepository.deleteObject(fileMeta);
     }
 }

+ 48 - 0
oss-store/src/main/java/cn/reghao/oss/store/service/SignService.java

@@ -0,0 +1,48 @@
+package cn.reghao.oss.store.service;
+
+import cn.reghao.jutil.jdk.security.RandomString;
+import cn.reghao.oss.api.util.JwtUtil;
+import cn.reghao.oss.store.util.SignatureUtil;
+import cn.reghao.oss.api.constant.ChannelAction;
+import cn.reghao.oss.api.dto.OssPayload;
+import com.github.benmanes.caffeine.cache.Cache;
+import org.springframework.stereotype.Service;
+
+import java.util.UUID;
+
+/**
+ * url 签名服务
+ *
+ * @author reghao
+ * @date 2023-10-18 14:16:01
+ */
+@Service
+public class SignService {
+    private final StoreLocalService storeLocalService;
+    private final Cache<String, String> cache;
+
+    public SignService(StoreLocalService storeLocalService, Cache<String, String> cache) {
+        this.storeLocalService = storeLocalService;
+        this.cache = cache;
+    }
+
+    public String getSignedUrl(int loginUser, String url, int expire) {
+        long timestamp = System.currentTimeMillis() + expire*1000L;
+        int channelId = storeLocalService.getChannelIdByUrl(loginUser, url);
+
+        String action1 = ChannelAction.download.getName();
+        String action = ChannelAction.access.getName();
+        OssPayload ossPayload = new OssPayload(action, channelId, loginUser);
+
+        //String secretKey = storeLocalService.getSecretKey();
+        String secretKey = RandomString.getString(128);
+        String token = JwtUtil.createToken(ossPayload, timestamp, secretKey);
+        cache.put(token, 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);
+    }
+}

+ 36 - 0
oss-store/src/main/java/cn/reghao/oss/store/service/StoreLocalService.java

@@ -0,0 +1,36 @@
+package cn.reghao.oss.store.service;
+
+import cn.reghao.oss.api.dto.NodeProperties;
+import cn.reghao.oss.api.iface.ConsoleService;
+import cn.reghao.oss.api.dto.StoreNodeDto;
+import cn.reghao.oss.api.dto.ObjectChannel;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author reghao
+ * @date 2024-03-08 09:20:27
+ */
+@Service
+public class StoreLocalService {
+    private final ConsoleService consoleService;
+
+    public StoreLocalService(ConsoleService consoleService) {
+        this.consoleService = consoleService;
+    }
+
+    public void register(StoreNodeDto storeNodeDto) {
+        consoleService.registerNode(storeNodeDto);
+    }
+
+    public NodeProperties getNodeProperties(String host) {
+        return consoleService.getNodeProperties(host);
+    }
+
+    public ObjectChannel getChannelById(int loginUser, int channelId) {
+        return consoleService.getChannelById(loginUser, channelId);
+    }
+
+    public int getChannelIdByUrl(int loginUser, String url) {
+        return consoleService.getChannelIdByUrl(loginUser, url);
+    }
+}

+ 190 - 0
oss-store/src/main/java/cn/reghao/oss/store/task/MediaFileProcessor.java

@@ -0,0 +1,190 @@
+package cn.reghao.oss.store.task;
+
+import cn.reghao.jutil.media.FFmpegWrapper;
+import cn.reghao.jutil.media.ImageOps;
+import cn.reghao.jutil.media.MediaQuality;
+import cn.reghao.jutil.media.MediaResolution;
+import cn.reghao.jutil.media.model.AudioProps;
+import cn.reghao.jutil.media.model.MediaProps;
+import cn.reghao.jutil.media.model.VideoProps;
+import cn.reghao.oss.api.dto.ObjectMeta;
+import cn.reghao.oss.api.dto.media.AudioInfo;
+import cn.reghao.oss.api.dto.media.ConvertedImageInfo;
+import cn.reghao.oss.api.dto.media.ImageInfo;
+import cn.reghao.oss.api.dto.media.VideoInfo;
+import cn.reghao.oss.store.db.repository.ObjectRepository;
+import cn.reghao.oss.store.model.vo.ObjectResult;
+import cn.reghao.oss.store.service.FileStoreService;
+import cn.reghao.oss.store.service.PutObjectService;
+import cn.reghao.oss.store.util.FileType;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2023-01-11 10:40:17
+ */
+@Slf4j
+@Service
+public class MediaFileProcessor {
+    private final ObjectRepository objectRepository;
+    private final FileStoreService fileStoreService;
+    private final PutObjectService putObjectService;
+
+    public MediaFileProcessor(ObjectRepository objectRepository, FileStoreService fileStoreService,
+                              PutObjectService putObjectService) {
+        this.objectRepository = objectRepository;
+        this.fileStoreService = fileStoreService;
+        this.putObjectService = putObjectService;
+    }
+
+    public VideoInfo getVideoInfo(String videoFileId) throws Exception {
+        ObjectMeta objectMeta = objectRepository.getObjectMetaById(videoFileId);
+        if (objectMeta == null) {
+            return null;
+        }
+
+        String absolutePath = objectMeta.getAbsolutePath();
+        MediaProps mediaProps = FFmpegWrapper.getMediaProps(absolutePath);
+        if (mediaProps == null) {
+            String errMsg = String.format("%s 的 FFmpeg 媒体信息为 null", videoFileId);
+            throw new Exception(errMsg);
+        }
+
+        VideoProps videoProps = mediaProps.getVideoProps();
+        if (videoProps == null) {
+            String errMsg = String.format("%s 的 FFmpeg 视频信息为 null", videoFileId);
+            throw new Exception(errMsg);
+        }
+
+        String videoCodec = videoProps.getCodecName();
+        long vbitRate = videoProps.getBitRate();
+
+        String audioCodec = null;
+        long abitRate = 0;
+        AudioProps audioProps1 = mediaProps.getAudioProps();
+        if (audioProps1 != null) {
+            audioCodec = audioProps1.getCodecName();
+            abitRate = audioProps1.getBitRate();
+        }
+
+        String objectId = videoFileId;
+        String objectName = objectMeta.getObjectName();
+        int width = videoProps.getCodedWidth().intValue();
+        int height = videoProps.getCodedHeight().intValue();
+        int duration = videoProps.getDuration().intValue();
+        MediaResolution mediaResolution = MediaQuality.getQuality(width, height);
+        String quality = mediaResolution.getQualityStr();
+        String urlType = FileType.getVideoUrlType(absolutePath);
+        LocalDateTime createTime = mediaProps.getCreateTime();
+
+        return new VideoInfo(videoFileId, objectId, videoCodec, vbitRate, audioCodec, abitRate,
+                urlType, objectName, quality, width, height, duration, createTime);
+    }
+
+    public ImageInfo getImageInfo(String imageFileId) throws Exception {
+        ObjectMeta objectMeta = objectRepository.getObjectMetaById(imageFileId);
+        if (objectMeta == null) {
+            return null;
+        }
+
+        String absolutePath = objectMeta.getAbsolutePath();
+        File file = new File(absolutePath);
+        String format = ImageOps.getFormat(file);
+        ImageOps.Size size = ImageOps.info(new File(absolutePath));
+        int width = size.getWidth();
+        int height = size.getHeight();
+        String objectId = imageFileId;
+        String objectName = objectMeta.getObjectName();
+
+        ImageInfo imageInfo = new ImageInfo(imageFileId, objectId, format, objectName, width, height);
+        return imageInfo;
+    }
+
+    public List<ConvertedImageInfo> getWebpInfos(List<String> imageFileIds) {
+        return imageFileIds.stream()
+                .map(imageFileId -> {
+                    try {
+                        return getWebpInfo(imageFileId);
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                    return null;
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+    }
+
+    public ConvertedImageInfo getWebpInfo(String imageFileId) throws Exception {
+        ObjectMeta objectMeta = objectRepository.getObjectMetaById(imageFileId);
+        if (objectMeta == null) {
+            log.error("{} not exist", imageFileId);
+            return null;
+        }
+
+        String originalObjectName = objectMeta.getObjectName();
+        String absolutePath = objectMeta.getAbsolutePath();
+        File srcFile = new File(absolutePath);
+
+        String format = "webp";
+        String suffix = "." + format;
+        String contentId = UUID.randomUUID().toString().replace("-", "");
+        String destPath = fileStoreService.genFilePath(contentId, srcFile.length(), suffix);
+        File destFile = new File(destPath);
+        ImageOps.convert2webp(srcFile, destFile);
+
+        if (destFile.exists()) {
+            ObjectResult objectResult1 = putObjectService.putObject(originalObjectName, contentId, suffix, destFile);
+            String objectName1 = objectResult1.getObjectName();
+            String objectId1 = objectResult1.getObjectId();
+            ConvertedImageInfo convertedImageInfo = new ConvertedImageInfo(imageFileId, objectId1, format, objectName1);
+            return convertedImageInfo;
+        } else {
+            log.error("image conversion failed");
+            return null;
+        }
+    }
+
+    public List<ImageInfo> getImagesInfo(List<String> imageFileIds) {
+        return imageFileIds.stream()
+                .map(imageFileId -> {
+                    try {
+                        return getImageInfo(imageFileId);
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                    return null;
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+    }
+
+    public AudioInfo getAudioInfo(String audioFileId) throws Exception {
+        ObjectMeta objectMeta = objectRepository.getObjectMetaById(audioFileId);
+        if (objectMeta == null) {
+            return null;
+        }
+
+        String absolutePath = objectMeta.getAbsolutePath();
+        MediaProps mediaProps = FFmpegWrapper.getMediaProps(absolutePath);
+        if (mediaProps == null || mediaProps.getAudioProps() == null) {
+            String errMsg = String.format("%s 的 FFmpeg 音频信息为 null", audioFileId);
+            throw new Exception(errMsg);
+        }
+
+        AudioProps audioProps = mediaProps.getAudioProps();
+        String audioCodec = audioProps.getCodecName();
+        int duration = audioProps.getDuration().intValue();
+        long bitRate = audioProps.getBitRate();
+        String objectId = audioFileId;
+        String objectName = objectMeta.getObjectName();
+        return new AudioInfo(audioFileId, objectId, duration, audioCodec, bitRate, objectName);
+    }
+}

+ 65 - 0
oss-store/src/main/java/cn/reghao/oss/store/task/VideoFile.java

@@ -0,0 +1,65 @@
+package cn.reghao.oss.store.task;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * 视频文件
+ *
+ * @author reghao
+ * @date 2021-11-22 10:21:15
+ */
+@NoArgsConstructor
+@Setter
+@Getter
+public class VideoFile {
+    // 原始文件的 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();
+    }
+}

+ 196 - 0
oss-store/src/main/java/cn/reghao/oss/store/task/VideoFileProcessor.java

@@ -0,0 +1,196 @@
+package cn.reghao.oss.store.task;
+
+import cn.reghao.jutil.jdk.security.DigestUtil;
+import cn.reghao.oss.api.dto.ObjectMeta;
+import cn.reghao.oss.store.db.repository.ObjectRepository;
+import cn.reghao.oss.store.model.vo.ObjectProp;
+import cn.reghao.oss.store.service.FileStoreService;
+import cn.reghao.oss.store.service.PutObjectService;
+import cn.reghao.jutil.media.model.AudioProps;
+import cn.reghao.jutil.media.model.MediaProps;
+import cn.reghao.jutil.media.model.VideoProps;
+import cn.reghao.oss.store.model.vo.ObjectResult;
+import cn.reghao.oss.store.util.FileType;
+import cn.reghao.oss.store.service.ObjectNameService;
+import cn.reghao.jutil.media.FFmpegWrapper;
+import cn.reghao.jutil.media.MediaQuality;
+import cn.reghao.jutil.media.MediaResolution;
+import cn.reghao.oss.api.rest.UploadFileRet;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * @author reghao
+ * @date 2023-01-11 10:40:17
+ */
+@Slf4j
+@Service
+public class VideoFileProcessor {
+    private final ObjectNameService objectNameService;
+    private final PutObjectService putObjectService;
+    private final ObjectRepository objectRepository;
+    private final FileStoreService fileStoreService;
+
+    public VideoFileProcessor(ObjectNameService objectNameService, PutObjectService putObjectService,
+                              ObjectRepository objectRepository, FileStoreService fileStoreService) {
+        this.objectNameService = objectNameService;
+        this.putObjectService = putObjectService;
+        this.objectRepository = objectRepository;
+        this.fileStoreService = fileStoreService;
+    }
+
+    public UploadFileRet process(ObjectResult objectResult) throws Exception {
+        String objectName = objectResult.getObjectName();
+        String objectId = objectResult.getObjectId();
+        String videoFileId = objectId;
+        boolean duplicate = objectResult.isDuplicate();
+        if (duplicate) {
+            String dupObjectId = objectResult.getDupObjectId();
+            //List<VideoFile> videoFiles = videoRepository.getVideoFiles(dupObjectId);
+            List<VideoFile> videoFiles = new ArrayList<>();
+            VideoFile videoFile = videoFiles.get(0);
+            VideoFile videoFile1 = new VideoFile(videoFileId, objectId, objectName, videoFile);
+            List<VideoFile> list = new ArrayList<>();
+            list.add(videoFile1);
+
+            if (videoFiles.size() > 1) {
+                for (int i = 1; i < videoFiles.size(); i++) {
+                    VideoFile videoFile2 = videoFiles.get(i);
+                    ObjectResult objectResult1 = putObjectService.copyFromObjectId(videoFile2.getObjectId());
+                    String objectId1 = objectResult1.getObjectId();
+                    String objectName1 = objectResult1.getObjectName();
+                    list.add(new VideoFile(videoFileId, objectId1, objectName1, videoFile2));
+                }
+            }
+
+            //videoRepository.saveVideoFiles(list);
+            return new UploadFileRet(videoFileId, null);
+        }
+
+        String absolutePath = objectResult.getAbsolutePath();
+        MediaProps mediaProps = FFmpegWrapper.getMediaProps(absolutePath);
+        if (mediaProps == null) {
+            String errMsg = String.format("%s 的 FFmpeg 媒体信息为 null", objectName);
+            throw new Exception(errMsg);
+        }
+
+        VideoProps videoProps = mediaProps.getVideoProps();
+        if (videoProps == null) {
+            String errMsg = String.format("%s 的 FFmpeg 视频信息为 null", objectName);
+            throw new Exception(errMsg);
+        }
+
+        String videoCodec = videoProps.getCodecName();
+        long vbitRate = videoProps.getBitRate();
+
+        String audioCodec = null;
+        long abitRate = 0;
+        AudioProps audioProps1 = mediaProps.getAudioProps();
+        if (audioProps1 != null) {
+            audioCodec = audioProps1.getCodecName();
+            abitRate = audioProps1.getBitRate();
+        }
+
+        /*if (videoCodecs.contains(videoCodec)) {
+            AudioProps audioProps = mediaProps.getAudioProps();
+            if (audioProps != null && !audioCodecs.contains(audioProps.getCodecName())) {
+                log.error("{} 对象的音频非 aac&mp3 编码, 暂不处理", objectName);
+                return null;
+            }
+        } else {
+            log.error("{} 对象的视频非 h264 编码, 暂不处理", objectName);
+            return null;
+        }*/
+        int width = videoProps.getCodedWidth().intValue();
+        int height = videoProps.getCodedHeight().intValue();
+        int duration = videoProps.getDuration().intValue();
+        MediaResolution mediaResolution = MediaQuality.getQuality(width, height);
+        String quality = mediaResolution.getQualityStr();
+        String urlType = FileType.getVideoUrlType(absolutePath);
+        VideoFile videoFile = new VideoFile(videoFileId, objectId, videoCodec, vbitRate, audioCodec, abitRate,
+                urlType, objectName, quality, width, height, duration);
+        //videoRepository.saveVideoFiles(List.of(videoFile));
+        return new UploadFileRet(videoFileId, null);
+    }
+
+    class ConvertVideoTask implements Runnable {
+        private final VideoFile videoFile;
+
+        public ConvertVideoTask(VideoFile videoFile) {
+            this.videoFile = videoFile;
+        }
+
+        public void run() {
+            String videoFileId = videoFile.getVideoFileId();
+            log.info("convert video file {}", videoFileId);
+            int width = videoFile.getWidth();
+            int height = videoFile.getHeight();
+
+            ObjectMeta objectMeta = objectRepository.getObjectMetaById(videoFileId);
+            String originalObjectName = objectMeta.getObjectName();
+            String srcPath = objectMeta.getAbsolutePath();
+            File srcFile = new File(srcPath);
+
+            String contentId = UUID.randomUUID().toString().replace("-", "");
+            String suffix = ".mp4";
+            String destPath = fileStoreService.genFilePath(contentId, srcFile.length(), suffix);
+            try {
+                File savedFile = new File(destPath);
+                if (savedFile.exists()) {
+                    throw new IOException(destPath + " exist");
+                }
+
+                String format = suffix.replace(".", "");
+                int ret = FFmpegWrapper.formatCovert(srcFile.getAbsolutePath(), destPath, format);
+                if (ret != 0) {
+                    throw new Exception("视频转码失败");
+                }
+                String sha256sum = DigestUtil.sha256sum(destPath);
+
+                ObjectProp objectProp = objectNameService.getObjectProp(originalObjectName, suffix);
+                String originalFilename = "converted_video";
+                ObjectResult objectResult = putObjectService.putObject(objectProp, contentId, savedFile, originalFilename, sha256sum);
+
+                String objectId = objectResult.getObjectId();
+                String urlType = FileType.getVideoUrlType(destPath);
+
+                MediaProps mediaProps = FFmpegWrapper.getMediaProps(destPath);
+                if (mediaProps == null) {
+                    log.error("{} 的 FFmpeg 媒体信息为 null", destPath);
+                    return;
+                }
+
+                VideoProps videoProps = mediaProps.getVideoProps();
+                if (videoProps == null) {
+                    log.error("{} 的 FFmpeg 视频信息为 null", destPath);
+                    return;
+                }
+
+                String videoCodec = videoProps.getCodecName();
+                long vbitRate = videoProps.getBitRate();
+
+                String audioCodec = null;
+                long abitRate = 0;
+                AudioProps audioProps1 = mediaProps.getAudioProps();
+                if (audioProps1 != null) {
+                    audioCodec = audioProps1.getCodecName();
+                    abitRate = audioProps1.getBitRate();
+                }
+
+                int duration = videoFile.getDuration();
+                MediaResolution mediaResolution = MediaQuality.getQuality(width, height);
+                String quality = mediaResolution.getQualityStr();
+                VideoFile videoFile1 = new VideoFile(videoFileId, objectId, videoCodec, vbitRate, audioCodec, abitRate,
+                        urlType, objectResult.getObjectName(), quality, width, height, duration);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}

+ 0 - 1
oss-store/src/main/java/cn/reghao/oss/store/util/SignatureUtil.java

@@ -1,7 +1,6 @@
 package cn.reghao.oss.store.util;
 
 import cn.reghao.jutil.jdk.converter.ByteHex;
-import org.checkerframework.checker.units.qual.A;
 
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;

+ 10 - 0
oss-store/src/main/resources/application-dev.yml

@@ -0,0 +1,10 @@
+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:
+  storeHost: 127.0.0.1
+  diskDirs:
+    - /opt/disk/13f654c8-af87-4710-aac9-7aa086c99aec/
+  consoleHost: 127.0.0.1

+ 11 - 0
oss-store/src/main/resources/application-test.yml

@@ -0,0 +1,11 @@
+spring:
+  datasource:
+    url: jdbc:mysql://192.168.0.210:3306/reghao_oss_tdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
+    username: test
+    password: Test@123456
+oss:
+  storeHost: 192.168.0.210
+  diskDirs:
+    - /opt/oss/disk/13f654c8-af87-4710-aac9-7aa086c99aec/
+    - /opt/oss/disk/40b2cf6e-1501-421a-b7b3-9cbf5b206d39/
+  consoleHost: 192.168.0.210

+ 6 - 2
oss-store/src/main/resources/application.yml

@@ -1,13 +1,15 @@
 dubbo:
   protocol:
     name: dubbo
-    port: 8110
+    port: 14010
   application:
     name: oss-store-provider
   scan:
     base-packages: cn.reghao.oss.store.rpc
+  registry:
+    address: N/A
 server:
-  port: 8010
+  port: 4010
   tomcat:
     connection-timeout: 1800_000
     keep-alive-timeout: 1800_000
@@ -18,6 +20,8 @@ spring:
       max-file-size: 20GB
   application:
     name: oss-store
+  profiles:
+    active: @profile.active@
   datasource:
     driver-class-name: com.mysql.cj.jdbc.Driver
     type: com.zaxxer.hikari.HikariDataSource

+ 17 - 35
oss-store/src/main/resources/logback-spring.xml

@@ -9,48 +9,30 @@
         </layout>
     </appender>
 
-    <!-- info 日志文件 -->
-    <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
-        <filter class="ch.qos.logback.classic.filter.LevelFilter">
-            <level>ERROR</level>
-            <onMatch>DENY</onMatch>
-            <onMismatch>ACCEPT</onMismatch>
-        </filter>
-        <encoder>
+    <!-- 运行日志 -->
+    <appender name="runtimeLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <layout class="ch.qos.logback.classic.PatternLayout">
             <pattern>
-                %d{HH:mm:ss.SSS} %-5level %c %M %L - %msg%n
+                %d{HH:mm:ss.SSS} [%thread] %-5level %c %M %L - %msg%n
             </pattern>
-            <charset>UTF-8</charset>
-        </encoder>
+        </layout>
         <!-- 滚动策略 -->
         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
             <fileNamePattern>
-                logs/info.%d.log
-            </fileNamePattern>
-        </rollingPolicy>
-    </appender>
-
-    <!-- error 日志文件 -->
-    <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
-        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
-            <level>ERROR</level>
-        </filter>
-        <encoder>
-            <pattern>
-                %d{HH:mm:ss.SSS} %-5level %c %M %L - %msg%n
-            </pattern>
-            <charset>UTF-8</charset>
-        </encoder>
-        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <fileNamePattern>
-                logs/error.%d.log
+                logs/oss-store.%d.log
             </fileNamePattern>
         </rollingPolicy>
     </appender>
 
-    <root level="info">
-        <!--<appender-ref ref="consoleLog"></appender-ref>-->
-        <appender-ref ref="fileInfoLog"></appender-ref>
-        <appender-ref ref="fileErrorLog"></appender-ref>
-    </root>
+    <!-- 根据 spring 的不同环境使用不同的日志配置 -->
+    <springProfile name="dev">
+        <root level="info">
+            <appender-ref ref="consoleLog"></appender-ref>
+        </root>
+    </springProfile>
+    <springProfile name="test">
+        <root level="info">
+            <appender-ref ref="runtimeLog"></appender-ref>
+        </root>
+    </springProfile>
 </configuration>

+ 4 - 4
oss-store/src/main/resources/mapper/DataBlockMapper.xml

@@ -4,16 +4,16 @@
 <mapper namespace="cn.reghao.oss.store.db.mapper.DataBlockMapper">
     <insert id="save" useGeneratedKeys="true" keyProperty="id">
         insert into data_block
-        (`id`,`deleted`,`create_time`,`update_time`,`content_id`,`index`,`block_id`,`host`,`base_dir`,`relative_dir`,`absolute_path`,`size`,`start`,`end`)
+        (`content_id`,`block_id`,`host`,`absolute_path`,`size`)
         values
-        (#{id},#{deleted},#{createTime},#{updateTime},#{contentId},#{index},#{blockId},#{host},#{baseDir},#{relativeDir},#{absolutePath},#{size},#{start},#{end})
+        (#{contentId},#{blockId},#{host},#{absolutePath},#{size})
     </insert>
     <insert id="saveAll" useGeneratedKeys="true" keyProperty="id">
         insert into data_block
-        (`id`,`deleted`,`create_time`,`update_time`,`content_id`,`index`,`block_id`,`host`,`base_dir`,`relative_dir`,`absolute_path`,`size`,`start`,`end`)
+        (`content_id`,`block_id`,`host`,`absolute_path`,`size`)
         values
         <foreach collection="list" item="item" index="index" separator=",">
-            (#{item.id},#{item.deleted},#{item.createTime},#{item.updateTime},#{item.contentId},#{item.index},#{item.blockId},#{item.host},#{item.baseDir},#{item.relativeDir},#{item.absolutePath},#{item.size},#{item.start},#{item.end})
+            (#{item.contentId},#{item.blockId},#{item.host},#{item.absolutePath},#{item.size})
         </foreach>
     </insert>
 

+ 18 - 66
oss-store/src/main/resources/mapper/FileMetaMapper.xml

@@ -4,16 +4,16 @@
 <mapper namespace="cn.reghao.oss.store.db.mapper.FileMetaMapper">
     <insert id="save" useGeneratedKeys="true" keyProperty="id">
         insert into file_meta
-        (`id`,`deleted`,`create_time`,`update_time`,`object_name`,`object_id`,`content_id`,`pid`,`filename`,`size`,`file_type`,`content_type`,`sha256sum`,`upload_by`,`disk_file`,`scope`)
+        (`id`,`deleted`,`create_time`,`update_time`,`object_name`,`object_id`,`content_id`,`pid`,`filename`,`size`,`file_type`,`content_type`,`sha256sum`,`upload_by`,`scope`)
         values
-        (#{id},#{deleted},#{createTime},#{updateTime},#{objectName},#{objectId},#{contentId},#{pid},#{filename},#{size},#{fileType},#{contentType},#{sha256sum},#{uploadBy},#{diskFile},#{scope})
+        (#{id},#{deleted},#{createTime},#{updateTime},#{objectName},#{objectId},#{contentId},#{pid},#{filename},#{size},#{fileType},#{contentType},#{sha256sum},#{uploadBy},#{scope})
     </insert>
     <insert id="saveAll" useGeneratedKeys="true" keyProperty="id">
         insert into file_meta
-        (`id`,`deleted`,`create_time`,`update_time`,`object_name`,`object_id`,`content_id`,`pid`,`filename`,`size`,`file_type`,`content_type`,`sha256sum`,`upload_by`,`disk_file`,`scope`)
+        (`id`,`deleted`,`create_time`,`update_time`,`object_name`,`object_id`,`content_id`,`pid`,`filename`,`size`,`file_type`,`content_type`,`sha256sum`,`upload_by`,`scope`)
         values
         <foreach collection="list" item="item" index="index" separator=",">
-            (#{item.id},#{item.deleted},#{item.createTime},#{item.updateTime},#{item.objectName},#{item.objectId},#{item.contentId},#{item.pid},#{item.filename},#{item.size},#{item.fileType},#{item.contentType},#{item.sha256sum},#{item.uploadBy},#{item.diskFile},#{item.scope})
+            (#{item.id},#{item.deleted},#{item.createTime},#{item.updateTime},#{item.objectName},#{item.objectId},#{item.contentId},#{item.pid},#{item.filename},#{item.size},#{item.fileType},#{item.contentType},#{item.sha256sum},#{item.uploadBy},#{item.scope})
         </foreach>
     </insert>
 
@@ -27,13 +27,10 @@
         set object_name=#{objectName}
         where object_id=#{objectId}
     </update>
-    <update id="updateScopeByObjectIds">
+    <update id="updateScopeByObjectName">
         update file_meta
         set scope=#{scope}
-        where object_id in
-        <foreach collection="list" item="item" separator="," open="(" close=")">
-            #{item}
-        </foreach>
+        where object_name=#{objectName}
     </update>
     <update id="updateScopeByObjectNames">
         update file_meta
@@ -67,25 +64,15 @@
         select * from file_meta
     </select>
     <select id="findAll0" resultType="cn.reghao.oss.store.model.po.FileMeta">
-        select file_meta.* from file_meta
-        inner join bucket
-        on bucket.name=#{bucket}
-        and object_name regexp #{regex}
-        limit #{max}
-    </select>
-    <!-- TODO ${prefix} 和 #{prefix} 的区别 -->
-    <select id="findAll1" resultType="cn.reghao.oss.store.model.po.FileMeta">
-        select file_meta.* from file_meta
-        inner join bucket
-        on bucket.name=#{bucket}
-        and object_name regexp concat_ws('', '^', '${prefix}', '([^/])+/?$')
+        select file_meta.*
+        from file_meta
+        where object_name regexp #{regex}
         limit #{max}
     </select>
     <select id="findAll2" resultType="cn.reghao.oss.store.model.po.FileMeta">
-        select file_meta.* from file_meta
-        inner join bucket
-        on bucket.name=#{bucket}
-        and object_name regexp concat_ws('', '^', '${prefix}', '([^/])+/?$')
+        select file_meta.*
+        from file_meta
+        where object_id=#{objectId} and object_name regexp concat_ws('', '^', '${prefix}', '([^/])+/?$')
         and id > (select id from file_meta where object_name=#{start})
         limit #{max}
     </select>
@@ -144,6 +131,12 @@
             #{id}
         </foreach>
     </select>
+    <select id="findByPid" resultType="cn.reghao.oss.api.dto.FileInfo">
+        select size,object_name,object_id,file_type,filename,update_time
+        from file_meta
+        where pid=#{pid}
+        limit #{pageSize}
+    </select>
 
     <!--*************************************************************************************************************-->
     <update id="updateFilename">
@@ -162,41 +155,13 @@
         from file_meta
         where pid=#{pid}
     </select>
-    <select id="findFileInfoByPage" resultType="cn.reghao.oss.api.dto.FileInfo">
-        select file_meta.object_id as file_id,file_meta.filename,file_meta.file_type as type,file_meta.size,file_meta.update_time,file_type.icon
-        from file_meta
-        inner join file_type
-        on file_meta.deleted=0 and file_meta.file_type=file_type.id and file_meta.pid=#{pid}
-        order by file_type asc
-    </select>
-    <select id="findFileInfo1ByPage" resultType="cn.reghao.oss.api.dto.FileInfo">
-        select file_meta.object_id as file_id,file_meta.filename,file_meta.file_type as type,file_meta.size,file_meta.update_time,file_type.icon_large as icon
-        from file_meta
-        inner join file_type
-        on file_meta.deleted=0 and file_meta.file_type=file_type.id and file_meta.pid=#{pid}
-        order by file_type asc
-    </select>
 
     <select id="countDeletedFiles" resultType="java.lang.Integer">
         select count(*)
         from file_meta
         where deleted=1
     </select>
-    <select id="findDeletedFileByPage" resultType="cn.reghao.oss.api.dto.DeleteFile">
-        select file_meta.object_id as file_id,file_meta.filename,file_meta.file_type as type,file_meta.size,file_meta.update_time,file_type.icon
-        from file_meta
-        inner join file_type
-        on file_meta.deleted=1 and file_meta.file_type=file_type.id
-        order by file_type asc
-    </select>
 
-    <select id="findDirectories" resultType="cn.reghao.oss.api.dto.FileInfo">
-        select file_meta.object_id as file_id,file_meta.filename,file_meta.file_type as type,file_meta.size,file_meta.update_time,file_type.icon
-        from file_meta
-        inner join file_type
-        on file_meta.deleted=0 and file_meta.file_type=file_type.id and file_meta.file_type=1000 and file_meta.pid=#{pid}
-        order by file_type asc
-    </select>
     <select id="findByFilename" resultType="cn.reghao.oss.store.model.po.FileMeta">
         select *
         from file_meta
@@ -210,25 +175,12 @@
             #{id}
         </foreach>
     </select>
-    <select id="findByPid" resultType="cn.reghao.oss.store.model.po.FileMeta">
-        select *
-        from file_meta
-        where pid=#{pid}
-    </select>
 
     <select id="countByKeyword" resultType="java.lang.Integer">
         select count(*)
         from file_meta
         where filename like concat('%',#{keyword},'%')
     </select>
-    <select id="findKeywordByPage" resultType="cn.reghao.oss.api.dto.FileInfo">
-        select file_meta.object_id as file_id,file_meta.filename,file_meta.file_type as type,file_meta.size,file_meta.update_time,file_type.icon
-        from file_meta
-        inner join file_type
-        on file_meta.deleted=0 and file_meta.file_type=file_type.id and file_meta.filename like concat('%',#{keyword},'%')
-        order by file_type asc
-    </select>
-
     <select id="findFileMetaByPage" resultType="cn.reghao.oss.store.model.po.FileMeta">
         select * from file_meta
         where file_type!=1000

+ 1 - 162
oss-store/src/test/java/FileMetaTest.java

@@ -2,17 +2,7 @@ import cn.reghao.oss.store.OssStoreApplication;
 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.jutil.jdk.store.SubDirCount;
-import cn.reghao.oss.store.service.ObjectNameService;
-import cn.reghao.oss.store.util.UserContext;
-import cn.reghao.jutil.jdk.db.Page;
-import cn.reghao.jutil.jdk.security.DigestUtil;
-import cn.reghao.jutil.jdk.thread.ThreadPoolWrapper;
-import cn.reghao.oss.api.constant.ObjectScope;
-import cn.reghao.oss.api.constant.UploadChannel;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.io.FileUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -20,16 +10,7 @@ import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.context.junit4.SpringRunner;
 
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.FileVisitResult;
-import java.nio.file.FileVisitor;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.security.NoSuchAlgorithmException;
 import java.util.List;
-import java.util.concurrent.ExecutorService;
 
 /**
  * @author reghao
@@ -59,113 +40,6 @@ public class FileMetaTest {
         }
     }
 
-    /**
-     * 分页处理
-     *
-     * @param
-     * @return
-     * @date 2023-05-30 00:26:07
-     */
-    @Test
-    public void processByPage() {
-        int pageSize = 10_000;
-        int pageNumber = 1;
-        Page page = new Page(pageNumber, pageSize);
-        List<FileMeta> list = fileMetaMapper.findFileMetaByPage(page);
-        while (!list.isEmpty()) {
-            list.forEach(fileMeta -> {
-
-            });
-
-            pageNumber++;
-            page = new Page(pageNumber, pageSize);
-            list = fileMetaMapper.findFileMetaByPage(page);
-            log.info("page -> {}", pageNumber);
-        }
-    }
-
-    public void walkDir(Path path) throws IOException {
-        Files.walkFileTree(path, new FileVisitor<>() {
-            @Override
-            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
-                return FileVisitResult.CONTINUE;
-            }
-
-            @Override
-            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                String absolutePath = file.toString();
-                process(absolutePath);
-                return FileVisitResult.CONTINUE;
-            }
-
-            @Override
-            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
-                return FileVisitResult.CONTINUE;
-            }
-
-            @Override
-            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
-                return FileVisitResult.CONTINUE;
-            }
-        });
-    }
-
-    private void process(String absolutePath) {
-        try {
-            String sha256sum = DigestUtil.sha256sum(absolutePath);
-            FileMeta fileMeta = fileMetaMapper.findBySha256sum(sha256sum);
-            if (fileMeta != null) {
-                FileUtils.deleteQuietly(new File(absolutePath));
-                log.info("{} 删除 {} -> {}", Thread.currentThread().getName(), total++, absolutePath);
-            } else {
-                //log.info("{} 在 db 中不存在", absolutePath);
-            }
-        } catch (IOException | NoSuchAlgorithmException e) {
-            e.printStackTrace();
-        }
-    }
-
-    ExecutorService threadPool = ThreadPoolWrapper.threadPool("delete-pool", 10);
-    int total = 1;
-    @Test
-    public void deleteFile() throws IOException, InterruptedException {
-        String baseDir = "";
-        Path path = Path.of(baseDir);
-        //walkDir(path);
-
-        threadPool.submit(new Task(baseDir));
-        Thread.sleep(3600_000*24*7);
-    }
-
-    class Task implements Runnable {
-        private final String baseDir;
-
-        public Task(String baseDir) {
-            this.baseDir = baseDir;
-        }
-
-        @Override
-        public void run() {
-            Path path = Path.of(baseDir);
-            try {
-                walkDir(path);
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        }
-    }
-
-    @Autowired
-    ObjectNameService objectNameService;
-    @Test
-    public void initUploadChannel() {
-        UserContext userContext = new UserContext(10001L);
-        for (UploadChannel channel : UploadChannel.values()) {
-            String objectName = channel.getPrefix();
-            objectNameService.createParentDirs(objectName, ObjectScope.PUBLIC.getCode());
-        }
-    }
-
     @Autowired
     DataBlockMapper dataBlockMapper;
     @Test
@@ -174,46 +48,11 @@ public class FileMetaTest {
         int nextId = 0;
         List<DataBlock> list = dataBlockMapper.findDataBlocks(pageSize, nextId);
         while (!list.isEmpty()) {
-            process1(list);
-            dataBlockMapper.updateBatch(list);
+            // process DataBlocks
 
             nextId = list.get(list.size()-1).getId();
             list = dataBlockMapper.findDataBlocks(pageSize, nextId);
             log.info("nextId -> {}", nextId);
         }
     }
-
-    private void process(List<DataBlock> list) {
-        for (DataBlock dataBlock : list) {
-            String contentId = dataBlock.getContentId();
-            List<FileMeta> list1 = fileMetaMapper.findByContentId(contentId);
-            if (list1.isEmpty()) {
-                log.info("{} not exist in file_meta", contentId);
-            }
-        }
-    }
-
-    private void process1(List<DataBlock> list) {
-        for (DataBlock dataBlock : list) {
-            int id = dataBlock.getId();
-            String absolutePath = dataBlock.getAbsolutePath();
-            int idx = absolutePath.lastIndexOf("/");
-            String parent = absolutePath.substring(0, idx);
-            String relativeDir = parent.replace("", "");
-            dataBlock.setRelativeDir(relativeDir);
-
-            /*String contentId = dataBlock.getContentId();
-            List<FileMeta> list1 = fileMetaMapper.findByContentId(contentId);
-            if (list1.isEmpty()) {
-                log.info("{} not exist in file_meta", contentId);
-            }*/
-        }
-    }
-
-    @Test
-    public void test123() {
-        String baseDir = "";
-        List<SubDirCount> list = dataBlockMapper.findSubDirCount("");
-        System.out.println();
-    }
 }

+ 30 - 0
pom.xml

@@ -0,0 +1,30 @@
+<?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</artifactId>
+    <version>1.0.0</version>
+    <packaging>pom</packaging>
+    <modules>
+        <module>oss-api</module>
+        <module>oss-sdk</module>
+        <module>oss-store</module>
+    </modules>
+
+    <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>
+
+    <distributionManagement>
+        <repository>
+            <id>maven-local-hosted</id>
+            <url>http://nexus.reghao.cn/repository/maven-local-hosted/</url>
+        </repository>
+    </distributionManagement>
+</project>

+ 17 - 9
zzz/build.sh

@@ -1,16 +1,24 @@
 #!/bin/bash
 
-#cd /home/reghao/code/java/oss/oss-api/
-#mvn clean install -Dmaven.test.skip
-#
-#cd /home/reghao/code/java/oss/oss-sdk/
-#mvn clean install -Dmaven.test.skip
-
-cd /home/reghao/code/java/oss/oss-api/
-mvn clean deploy -Dmaven.test.skip
+cd /home/reghao/code/java/oss/store-api/
+mvn clean install -Dmaven.test.skip
+#mvn clean deploy -Dmaven.test.skip
+mvn clean
 
 cd /home/reghao/code/java/oss/oss-sdk/
-mvn clean deploy -Dmaven.test.skip
+mvn clean install -Dmaven.test.skip
+#mvn clean deploy -Dmaven.test.skip
+mvn clean
 
 cd /home/reghao/code/java/oss/oss-store/
 mvn clean package -Dmaven.test.skip
+mvn clean
+
+cd /home/reghao/code/java/oss/oss-console/
+mvn clean package -Dmaven.test.skip
+docker build -t oss-console .
+mvn clean
+
+mvn clean install -Dmaven.test.skip=true -am -pl oss-api
+mvn clean install -Dmaven.test.skip=true -am -pl oss-sdk
+mvn clean package -Dmaven.test.skip=true -am -pl oss-store