Forráskód Böngészése

添加一个 oss-media 模块, 用于处理 oss 中存储文件的信息

reghao 1 éve
szülő
commit
a3a74c61c5
29 módosított fájl, 1329 hozzáadás és 0 törlés
  1. 1 0
      oss-api/src/main/java/cn/reghao/oss/api/dto/ObjectMeta.java
  2. 7 0
      oss-media/Dockerfile
  3. 5 0
      oss-media/bin/shutdown.sh
  4. 5 0
      oss-media/bin/start.sh
  5. 167 0
      oss-media/pom.xml
  6. 11 0
      oss-media/src/main/java/cn/reghao/oss/store/OssMediaApplication.java
  7. 68 0
      oss-media/src/main/java/cn/reghao/oss/store/config/mybatis/DataSourceConfig.java
  8. 83 0
      oss-media/src/main/java/cn/reghao/oss/store/config/mybatis/PageListInterceptor.java
  9. 35 0
      oss-media/src/main/java/cn/reghao/oss/store/config/mybatis/SpringLifecycle.java
  10. 18 0
      oss-media/src/main/java/cn/reghao/oss/store/db/mapper/DataBlockMapper.java
  11. 35 0
      oss-media/src/main/java/cn/reghao/oss/store/db/mapper/FileMetaMapper.java
  12. 92 0
      oss-media/src/main/java/cn/reghao/oss/store/db/repository/ObjectRepository.java
  13. 15 0
      oss-media/src/main/java/cn/reghao/oss/store/model/dto/ContentRange.java
  14. 18 0
      oss-media/src/main/java/cn/reghao/oss/store/model/dto/PathUrl.java
  15. 35 0
      oss-media/src/main/java/cn/reghao/oss/store/model/po/DataBlock.java
  16. 74 0
      oss-media/src/main/java/cn/reghao/oss/store/model/po/FileMeta.java
  17. 13 0
      oss-media/src/main/java/cn/reghao/oss/store/model/vo/ImageObject.java
  18. 16 0
      oss-media/src/main/java/cn/reghao/oss/store/model/vo/ObjectProp.java
  19. 36 0
      oss-media/src/main/java/cn/reghao/oss/store/model/vo/ObjectResult.java
  20. 126 0
      oss-media/src/main/java/cn/reghao/oss/store/task/FileProcessor.java
  21. 5 0
      oss-media/src/main/resources/application-dev.yml
  22. 5 0
      oss-media/src/main/resources/application-test.yml
  23. 24 0
      oss-media/src/main/resources/application.yml
  24. 38 0
      oss-media/src/main/resources/logback-spring.xml
  25. 65 0
      oss-media/src/main/resources/mapper/DataBlockMapper.xml
  26. 204 0
      oss-media/src/main/resources/mapper/FileMetaMapper.xml
  27. 22 0
      oss-media/src/test/java/DiskTest.java
  28. 105 0
      oss-media/src/test/java/FileMetaTest.java
  29. 1 0
      pom.xml

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

@@ -14,6 +14,7 @@ import java.io.Serializable;
 public class ObjectMeta implements Serializable {
     private static final long serialVersionUID = 1L;
 
+    private int id;
     private String contentId;
     private String objectName;
     private String objectId;

+ 7 - 0
oss-media/Dockerfile

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

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

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

+ 5 - 0
oss-media/bin/start.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+app_dir=`pwd`
+app_name='oss-media.jar'
+java -jar ${app_dir}"/"${app_name} > console.log 2>&1 &

+ 167 - 0
oss-media/pom.xml

@@ -0,0 +1,167 @@
+<?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>
+    <parent>
+        <artifactId>oss</artifactId>
+        <groupId>cn.reghao.oss</groupId>
+        <version>1.0.0</version>
+    </parent>
+
+    <artifactId>oss-media</artifactId>
+    <version>1.0.0</version>
+    <packaging>jar</packaging>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>2.3.9.RELEASE</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.reghao.jutil</groupId>
+            <artifactId>jdk</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.reghao.jutil</groupId>
+            <artifactId>tool</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.reghao.jutil</groupId>
+            <artifactId>media</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.reghao.oss</groupId>
+            <artifactId>oss-api</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.reghao.tnb.mall</groupId>
+            <artifactId>mall-api</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.6</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+            <version>1.2.3</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.2.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+            <version>1.3.2</version>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.17</version>
+        </dependency>
+        <dependency>
+            <groupId>com.zaxxer</groupId>
+            <artifactId>HikariCP</artifactId>
+            <version>3.3.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.tika</groupId>
+            <artifactId>tika-core</artifactId>
+            <version>2.9.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.drewnoakes</groupId>
+            <artifactId>metadata-extractor</artifactId>
+            <version>2.18.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-imaging</artifactId>
+            <version>1.0-alpha3</version>
+        </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-media</finalName>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>application.yml</include>
+                    <include>application-${profile.active}.yml</include>
+                    <include>mapper/**</include>
+                    <include>*.xml</include>
+                </includes>
+            </resource>
+        </resources>
+
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.3.9.RELEASE</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

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

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

+ 68 - 0
oss-media/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-media/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");
+    }
+}

+ 35 - 0
oss-media/src/main/java/cn/reghao/oss/store/config/mybatis/SpringLifecycle.java

@@ -0,0 +1,35 @@
+package cn.reghao.oss.store.config.mybatis;
+
+import cn.reghao.oss.store.task.FileProcessor;
+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;
+
+/**
+ * @author reghao
+ * @date 2022-03-23 09:22:01
+ */
+@Slf4j
+@Component
+public class SpringLifecycle implements ApplicationRunner, DisposableBean {
+    private final FileProcessor fileProcessor;
+
+    public SpringLifecycle(FileProcessor fileProcessor) {
+        this.fileProcessor = fileProcessor;
+    }
+
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        log.info("oss-media start...");
+        log.info("执行文件任务...");
+        fileProcessor.process();
+        log.info("文件任务执行完成...");
+    }
+
+    @Override
+    public void destroy() {
+        log.info("oss-media shutdown...");
+    }
+}

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

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

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

@@ -0,0 +1,35 @@
+package cn.reghao.oss.store.db.mapper;
+
+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 org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2022-11-24 11:11:06
+ */
+@Mapper
+public interface FileMetaMapper extends BaseMapper<FileMeta> {
+    void updateScopeByObjectName(@Param("scope") int scope, @Param("objectName") String objectName);
+
+    FileMeta findBySha256sum(String sha256sum);
+    FileMeta findByObjectName(String objectName);
+    FileMeta findByObjectId(String objectId);
+    List<FileMeta> findByContentId(String contentId);
+    ObjectMeta findObjectMeta(String objectName);
+    ObjectMeta findObjectMetaById(String objectName);
+    List<FileInfo> findByPid(@Param("pid") String pid, @Param("pageSize") int pageSize, @Param("nextId") String nextId);
+    List<ObjectMeta> findByPrefix(@Param("prefix") String pid,
+                                  @Param("pageSize") int pageSize,
+                                  @Param("nextId") int nextId);
+
+    /******************************************************************************************************************/
+    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);
+}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 126 - 0
oss-media/src/main/java/cn/reghao/oss/store/task/FileProcessor.java

@@ -0,0 +1,126 @@
+package cn.reghao.oss.store.task;
+
+import cn.reghao.jutil.jdk.http.WebRequest;
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
+import cn.reghao.jutil.tool.http.DefaultWebRequest;
+import cn.reghao.oss.api.dto.ObjectMeta;
+import cn.reghao.oss.store.db.mapper.FileMetaMapper;
+import cn.reghao.tnb.mall.api.dto.MallReplyPhotoDto;
+import com.drew.imaging.ImageMetadataReader;
+import com.drew.lang.GeoLocation;
+import com.drew.metadata.Metadata;
+import com.drew.metadata.Tag;
+import com.drew.metadata.exif.ExifIFD0Directory;
+import com.drew.metadata.exif.GpsDirectory;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2024-09-07 11:46:45
+ */
+@Slf4j
+@Service
+public class FileProcessor {
+    private final FileMetaMapper fileMetaMapper;
+    private final WebRequest webRequest;
+    private String api = "http://api.reghao.cn/api/mall/map/photo";
+
+    public FileProcessor(FileMetaMapper fileMetaMapper) {
+        this.fileMetaMapper = fileMetaMapper;
+        this.webRequest = new DefaultWebRequest();
+    }
+
+    public void process() {
+        start();
+    }
+
+    private void start() {
+        int pageSize = 10000;
+        int nextId = 0;
+        String prefix = "image/p/%";
+        List<ObjectMeta> list = fileMetaMapper.findByPrefix(prefix, pageSize, nextId);
+
+        while (!list.isEmpty()) {
+            List<MallReplyPhotoDto> photoDtos = list.stream()
+                    .map(this::process)
+                    .filter(mallReplyPhotoDto -> mallReplyPhotoDto.getLongitude() != null)
+                    .collect(Collectors.toList());
+            if (!photoDtos.isEmpty()) {
+                try {
+                    webRequest.postJson(api, JsonConverter.objectToJson(photoDtos));
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+
+            nextId = list.get(list.size()-1).getId();
+            list = fileMetaMapper.findByPrefix(prefix, pageSize, nextId);
+            log.info("nextId -> {}", nextId);
+        }
+    }
+
+    private MallReplyPhotoDto process(ObjectMeta objectMeta) {
+        String objectId = objectMeta.getObjectId();
+        String photoUrl = String.format("//oss.reghao.cn/%s", objectMeta.getObjectName());
+        String absolutePath = objectMeta.getAbsolutePath();
+
+        File file = new File(absolutePath);
+        MallReplyPhotoDto mallReplyPhotoDto = new MallReplyPhotoDto("1", objectId, 105, photoUrl);
+        try {
+            Metadata metadata = ImageMetadataReader.readMetadata(file);
+            /*for (Directory directory : metadata.getDirectories()) {
+                for (Tag tag : directory.getTags()) {
+                    String tagName = tag.getTagName();
+                    String desc = tag.getDescription();
+                    System.out.printf("%s -> %s\n", tagName, desc);
+                }
+            }*/
+
+            Collection<ExifIFD0Directory> exifDirectories = metadata.getDirectoriesOfType(ExifIFD0Directory.class);
+            for (ExifIFD0Directory exif : exifDirectories) {
+                for (Tag tag : exif.getTags()) {
+                    String tagName = tag.getTagName();
+                    String desc = tag.getDescription();
+                    switch (tagName) {
+                        case "Make":
+                            mallReplyPhotoDto.setManufacturer(desc);
+                            break;
+                        case "Model":
+                            mallReplyPhotoDto.setModel(desc);
+                            break;
+                        case "Software":
+                            mallReplyPhotoDto.setSoftware(desc);
+                            break;
+                        case "Date/Time":
+                            String[] arr = desc.split(" ");
+                            String date = arr[0].replace(":", "-");
+                            String dateTime = String.format("%s %s", date, arr[1]);
+                            mallReplyPhotoDto.setShotAt(dateTime);
+                            break;
+                    }
+                }
+            }
+
+            Collection<GpsDirectory> gpsDirectories = metadata.getDirectoriesOfType(GpsDirectory.class);
+            for(GpsDirectory gps : gpsDirectories) {
+                GeoLocation geoLocation = gps.getGeoLocation();
+                if (geoLocation != null) {
+                    double longitude = geoLocation.getLongitude();
+                    double latitude = geoLocation.getLatitude();
+                    mallReplyPhotoDto.setLongitude(longitude);
+                    mallReplyPhotoDto.setLatitude(latitude);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return mallReplyPhotoDto;
+    }
+}

+ 5 - 0
oss-media/src/main/resources/application-dev.yml

@@ -0,0 +1,5 @@
+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

+ 5 - 0
oss-media/src/main/resources/application-test.yml

@@ -0,0 +1,5 @@
+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

+ 24 - 0
oss-media/src/main/resources/application.yml

@@ -0,0 +1,24 @@
+spring:
+  application:
+    name: oss-media
+  profiles:
+    active: @profile.active@
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    type: com.zaxxer.hikari.HikariDataSource
+    hikari:
+      minimum-idle: 5
+      maximum-pool-size: 10
+      auto-commit: true
+      idle-timeout: 30000
+      pool-name: EvaluationHikariCP
+      max-lifetime: 1800000
+      connection-timeout: 30000
+      connection-test-query: SELECT 1
+mybatis:
+  configuration:
+    map-underscore-to-camel-case: true
+    # mybatis sql 执行日志
+#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  mapper-locations: classpath*:mapper/**.xml
+  type-aliases-package: cn.reghao.oss.media.model.po

+ 38 - 0
oss-media/src/main/resources/logback-spring.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<configuration>
+    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
+        <layout class="ch.qos.logback.classic.PatternLayout">
+            <pattern>
+                %d{HH:mm:ss.SSS} [%thread] %-5level %c %M %L - %msg%n
+            </pattern>
+        </layout>
+    </appender>
+
+    <!-- 运行日志 -->
+    <appender name="runtimeLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <layout class="ch.qos.logback.classic.PatternLayout">
+            <pattern>
+                %d{HH:mm:ss.SSS} [%thread] %-5level %c %M %L - %msg%n
+            </pattern>
+        </layout>
+        <!-- 滚动策略 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>
+                logs/oss-media.%d.log
+            </fileNamePattern>
+        </rollingPolicy>
+    </appender>
+
+    <!-- 根据 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>

+ 65 - 0
oss-media/src/main/resources/mapper/DataBlockMapper.xml

@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="cn.reghao.oss.store.db.mapper.DataBlockMapper">
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        insert into data_block
+        (`content_id`,`block_id`,`host`,`absolute_path`,`size`)
+        values
+        (#{contentId},#{blockId},#{host},#{absolutePath},#{size})
+    </insert>
+    <insert id="saveAll" useGeneratedKeys="true" keyProperty="id">
+        insert into data_block
+        (`content_id`,`block_id`,`host`,`absolute_path`,`size`)
+        values
+        <foreach collection="list" item="item" index="index" separator=",">
+            (#{item.contentId},#{item.blockId},#{item.host},#{item.absolutePath},#{item.size})
+        </foreach>
+    </insert>
+
+    <delete id="delete">
+        delete from data_block
+        where content_id=#{contentId}
+    </delete>
+
+    <select id="findDataBlocks" resultType="cn.reghao.oss.store.model.po.DataBlock">
+        select *
+        from data_block
+        where id>#{nextId}
+        limit #{pageSize}
+    </select>
+
+    <select id="findByContentId" resultType="cn.reghao.oss.store.model.po.DataBlock">
+        select *
+        from data_block
+        where content_id=#{contentId}
+    </select>
+    <select id="findSubDirCount" resultType="cn.reghao.jutil.jdk.store.SubDirCount">
+        select relative_dir,count(*) as total
+        from data_block
+        group by relative_dir
+        order by total asc
+    </select>
+
+    <update id="updateParent">
+        update data_block
+        set relative_dir=#{relativeDir}
+        where id=#{id}
+    </update>
+    <update id="updateBatch">
+        update data_block
+        <trim prefix="set" suffixOverrides=",">
+            <trim prefix="relative_dir =case" suffix="end,">
+                <foreach collection="list" item="item" index="index">
+                    when id=#{item.id} then #{item.relativeDir}
+                </foreach>
+            </trim>
+        </trim>
+        <where>
+            id in
+            <foreach collection="list" item="item" separator="," open="(" close=")">
+                #{item.id}
+            </foreach>
+        </where>
+    </update>
+</mapper>

+ 204 - 0
oss-media/src/main/resources/mapper/FileMetaMapper.xml

@@ -0,0 +1,204 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<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`,`scope`)
+        values
+        (#{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`,`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.scope})
+        </foreach>
+    </insert>
+
+    <delete id="delete">
+        delete from file_meta
+        where object_id=#{objectId}
+    </delete>
+
+    <update id="update">
+        update file_meta
+        set object_name=#{objectName}
+        where object_id=#{objectId}
+    </update>
+    <update id="updateScopeByObjectName">
+        update file_meta
+        set scope=#{scope}
+        where object_name=#{objectName}
+    </update>
+    <update id="updateScopeByObjectNames">
+        update file_meta
+        set scope=#{scope}
+        where object_name in
+        <foreach collection="list" item="item" separator="," open="(" close=")">
+            #{item}
+        </foreach>
+    </update>
+    <update id="updateBatch">
+        update file_meta
+        <trim prefix="set" suffixOverrides=",">
+            <trim prefix="content_id =case" suffix="end,">
+                <foreach collection="list" item="item" index="index">
+                    when object_id=#{item.objectId} then #{item.contentId}
+                </foreach>
+            </trim>
+        </trim>
+        <where>
+            object_id in
+            <foreach collection="list" item="item" separator="," open="(" close=")">
+                #{item.objectId}
+            </foreach>
+        </where>
+    </update>
+
+    <select id="count" resultType="java.lang.Integer">
+        select count(*) from file_meta
+    </select>
+    <select id="findAll" resultType="cn.reghao.oss.store.model.po.FileMeta">
+        select * from file_meta
+    </select>
+    <select id="findAll0" resultType="cn.reghao.oss.store.model.po.FileMeta">
+        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
+        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>
+
+    <select id="findBySha256sum" resultType="cn.reghao.oss.store.model.po.FileMeta">
+        select * from file_meta
+        where sha256sum=#{sha256sum}
+        order by create_time asc
+        limit 1
+    </select>
+    <select id="findByObjectName" resultType="cn.reghao.oss.store.model.po.FileMeta">
+        select *
+        from file_meta
+        where object_name=#{objectName}
+    </select>
+    <select id="findByObjectNames" resultType="cn.reghao.oss.store.model.po.FileMeta">
+        select *
+        from file_meta
+        where object_name in
+        <foreach collection="list" item="id" index="index" open="(" close=")" separator=",">
+            #{id}
+        </foreach>
+    </select>
+
+    <select id="findByObjectId" resultType="cn.reghao.oss.store.model.po.FileMeta">
+        select *
+        from file_meta
+        where object_id=#{objectId}
+    </select>
+    <select id="findByContentId" resultType="cn.reghao.oss.store.model.po.FileMeta">
+        select *
+        from file_meta
+        where content_id=#{contentId}
+    </select>
+    <select id="findObjectMeta" resultType="cn.reghao.oss.api.dto.ObjectMeta">
+        select file_meta.size,file_meta.content_type,file_meta.object_name,file_meta.object_id,file_meta.scope as scope,file_meta.upload_by,
+        data_block.absolute_path
+        from file_meta
+        inner join data_block
+        on file_meta.content_id=data_block.content_id
+        and file_meta.object_name=#{objectName}
+    </select>
+    <select id="findObjectMetaById" resultType="cn.reghao.oss.api.dto.ObjectMeta">
+        select file_meta.size,file_meta.content_type,file_meta.object_name,file_meta.object_id,file_meta.scope as scope,file_meta.upload_by,
+        data_block.absolute_path
+        from file_meta
+        inner join data_block
+        on file_meta.content_id=data_block.content_id
+        and file_meta.object_id=#{objectId}
+    </select>
+    <select id="findObjectNames" resultType="java.lang.String">
+        select object_name
+        from file_meta
+        where object_id in
+        <foreach collection="list" item="id" index="index" open="(" close=")" separator=",">
+            #{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">
+        update file_meta
+        set update_time=now() and filename=#{filename}
+        where object_id=#{objectId}
+    </update>
+    <update id="updateParent">
+        update file_meta
+        set update_time=now() and pid=#{pid}
+        where object_id=#{objectId}
+    </update>
+
+    <select id="countByPid" resultType="java.lang.Integer">
+        select count(*)
+        from file_meta
+        where pid=#{pid}
+    </select>
+
+    <select id="countDeletedFiles" resultType="java.lang.Integer">
+        select count(*)
+        from file_meta
+        where deleted=1
+    </select>
+
+    <select id="findByFilename" resultType="cn.reghao.oss.store.model.po.FileMeta">
+        select *
+        from file_meta
+        where pid=#{pid} and filename like concat(#{filename},'%')
+    </select>
+    <select id="findFileInfos" resultType="cn.reghao.oss.store.model.po.FileMeta">
+        select *
+        from file_meta
+        where object_id in
+        <foreach collection="objectIds" item="id" index="index" open="(" close=")" separator=",">
+            #{id}
+        </foreach>
+    </select>
+
+    <select id="countByKeyword" resultType="java.lang.Integer">
+        select count(*)
+        from file_meta
+        where filename like concat('%',#{keyword},'%')
+    </select>
+    <select id="findFileMetaByPage" resultType="cn.reghao.oss.store.model.po.FileMeta">
+        select * from file_meta
+        where file_type!=1000
+    </select>
+    <select id="findObjectPrefix" resultType="java.lang.String">
+        select * from file_meta
+        where file_type==1000
+    </select>
+
+    <select id="findByPrefix" resultType="cn.reghao.oss.api.dto.ObjectMeta">
+        select file_meta.id,file_meta.size,file_meta.content_type,file_meta.object_name,file_meta.object_id,file_meta.scope as scope,file_meta.upload_by,
+        data_block.absolute_path
+        from file_meta
+        inner join data_block
+        on file_meta.content_id=data_block.content_id
+        and file_meta.file_type=1001
+        and file_meta.id>#{nextId}
+        order by file_meta.id
+        limit #{pageSize}
+    </select>
+</mapper>

+ 22 - 0
oss-media/src/test/java/DiskTest.java

@@ -0,0 +1,22 @@
+import cn.reghao.jutil.jdk.shell.ShellExecutor;
+import cn.reghao.jutil.jdk.shell.ShellResult;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Test;
+
+/**
+ * @author reghao
+ * @date 2023-07-16 19:19:12
+ */
+@Slf4j
+public class DiskTest {
+    ShellExecutor shell = new ShellExecutor();
+
+    @Test
+    public void diskTest() {
+        String versionCmd = "/opt/env/java/maven/maven-3.6.0/bin/mvn -v";
+        ShellResult shellResult = shell.exec(versionCmd.split("\\s+"));
+        if (shellResult.getExitCode() == 0) {
+            System.out.println(shellResult.getResult());
+        }
+    }
+}

+ 105 - 0
oss-media/src/test/java/FileMetaTest.java

@@ -0,0 +1,105 @@
+import cn.reghao.oss.api.dto.ObjectMeta;
+import cn.reghao.oss.api.dto.media.ImageInfo;
+import cn.reghao.oss.api.rest.UploadFileRet;
+import cn.reghao.oss.store.OssMediaApplication;
+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 com.drew.imaging.ImageMetadataReader;
+import com.drew.lang.GeoLocation;
+import com.drew.metadata.Metadata;
+import com.drew.metadata.exif.GpsDirectory;
+import com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.swing.table.TableCellRenderer;
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-07-16 19:19:12
+ */
+@Slf4j
+@ActiveProfiles("dev")
+@SpringBootTest(classes = OssMediaApplication.class)
+@RunWith(SpringRunner.class)
+public class FileMetaTest {
+    @Autowired
+    FileMetaMapper fileMetaMapper;
+
+    // 正则表达式查询测试
+    public void regexpTest() {
+        String bucket = "";
+        String prefix = "abc/d/";
+        String startAfter = "abc/d/e";
+        Integer maxKeys = 10;
+
+        StringBuilder regex = new StringBuilder();
+        regex.append("^").append(prefix).append("([^/])+/?$");
+        if (startAfter.isBlank()) {
+            fileMetaMapper.findAll0(bucket, maxKeys, regex.toString());
+        } else {
+            fileMetaMapper.findAll2(bucket, prefix, startAfter, maxKeys);
+        }
+    }
+
+    @Test
+    public void fileTest() {
+        int pageSize = 10000;
+        int nextId = 0;
+        String prefix = "image/p/%";
+        List<ObjectMeta> list = fileMetaMapper.findByPrefix(prefix, pageSize, nextId);
+        while (!list.isEmpty()) {
+            list.forEach(this::process);
+
+            nextId = list.get(list.size()-1).getId();
+            list = fileMetaMapper.findByPrefix(prefix, pageSize, nextId);
+            log.info("nextId -> {}", nextId);
+        }
+    }
+
+    private void process(ObjectMeta objectMeta) {
+        String objectId = objectMeta.getObjectId();
+        String absolutePath = objectMeta.getAbsolutePath();
+        File file = new File(absolutePath);
+
+        try {
+            Metadata metadata = ImageMetadataReader.readMetadata(file);
+            Collection<GpsDirectory> gpsDirectories = metadata.getDirectoriesOfType(GpsDirectory.class);
+            for(GpsDirectory gps : gpsDirectories) {
+                GeoLocation geoLocation = gps.getGeoLocation();
+                if (geoLocation != null) {
+                    double longitude = geoLocation.getLongitude();
+                    double latitude = geoLocation.getLatitude();
+                    log.info("{}: ({}, {})", objectId, longitude, latitude);;
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Autowired
+    DataBlockMapper dataBlockMapper;
+    @Test
+    public void dataBlockTest() {
+        int pageSize = 10000;
+        int nextId = 0;
+        List<DataBlock> list = dataBlockMapper.findDataBlocks(pageSize, nextId);
+        while (!list.isEmpty()) {
+            // process DataBlocks
+
+            nextId = list.get(list.size()-1).getId();
+            list = dataBlockMapper.findDataBlocks(pageSize, nextId);
+            log.info("nextId -> {}", nextId);
+        }
+    }
+}

+ 1 - 0
pom.xml

@@ -12,6 +12,7 @@
         <module>oss-api</module>
         <module>oss-sdk</module>
         <module>oss-store</module>
+        <module>oss-media</module>
     </modules>
 
     <properties>