From 201a6617ac00be6a9965cda3a5aae6d62de2842d Mon Sep 17 00:00:00 2001
From: Jerry Yan <792602257@qq.com>
Date: Thu, 26 Dec 2024 19:34:20 +0800
Subject: [PATCH] =?UTF-8?q?=E5=BD=BB=E5=BA=95=E9=93=B2=E9=99=A4OSSUtil?=
 =?UTF-8?q?=EF=BC=8C=E6=8A=BD=E8=B1=A1=E3=80=81=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../ycwl/basic/config/FaceDetectConfig.java   |  11 +-
 .../java/com/ycwl/basic/config/OssConfig.java |  29 ----
 .../ycwl/basic/controller/FileController.java |  12 +-
 .../pc/FaceDetectLogController.java           |  30 +++++
 .../basic/controller/viid/ViidController.java |  25 +---
 .../pc/template/entity/TemplateEntity.java    |   2 +-
 .../com/ycwl/basic/service/FileService.java   |  36 -----
 .../impl/pc/FaceDetectLogServiceImpl.java     |  22 +++
 .../service/impl/pc/FaceServiceImpl.java      |  54 ++------
 .../impl/task/TaskFaceServiceImpl.java        |  37 ++++-
 .../impl/task/TaskTaskServiceImpl.java        |  16 +--
 .../service/pc/FaceDetectLogService.java      |  10 ++
 .../basic/service/task/TaskFaceService.java   |   3 +
 .../ycwl/basic/storage/StorageFactory.java    |  53 ++++++++
 .../storage/adapters/AStorageAdapter.java     |  46 +++++++
 .../basic/storage/adapters/AliOssAdapter.java | 102 ++++++++++++++
 .../storage/adapters/IStorageAdapter.java     |  21 +++
 .../storage/adapters/LocalStorageAdapter.java |  39 ++++++
 .../storage/entity/AliOssStorageConfig.java   |  21 +++
 .../basic/storage/entity/StorageConfig.java   |   5 +
 .../ycwl/basic/storage/enums/StorageType.java |  27 ++++
 .../exceptions/StorageConfigException.java    |   7 +
 .../storage/exceptions/StorageException.java  |   7 +
 .../exceptions/StorageUndefinedException.java |   7 +
 .../StorageUnsupportedException.java          |   7 +
 .../exceptions/UploadFileFailedException.java |   7 +
 .../starter/StorageAutoConfiguration.java     |  30 +++++
 .../starter/config/OverallStorageConfig.java  |  15 +++
 .../starter/config/StorageConfigItem.java     |  12 ++
 .../tests/TestInitializationSpeed.java        |  19 +++
 .../ycwl/basic/storage/utils/StorageUtil.java |  41 ++++++
 .../ycwl/basic/task/DynamicTaskGenerator.java |   8 +-
 .../com/ycwl/basic/task/VideoPieceGetter.java |  42 +++---
 .../java/com/ycwl/basic/utils/OssUtil.java    | 126 ------------------
 .../basic/videoTask/VideoTaskFactory.java     |   4 +
 .../videoTask/adapters/DefaultAdapter.java    |   4 +
 .../basic/videoTask/adapters/IAdapter.java    |   4 +
 .../chains/VideoTaskGeneratorChain.java       |  22 +++
 .../CheckFaceCountInterceptor.java            |  11 ++
 .../IVidTaskGenChainInterceptor.java          |   8 ++
 .../basic/videoTask/daos/TemplateDao.java     |   4 +
 .../entity/VideoTaskGeneratorContext.java     |   4 +
 .../enums/VideoTaskTriggerReason.java         |  22 +++
 src/main/resources/application-dev.yml        |  35 +++--
 src/main/resources/application-prod.yml       |  35 +++--
 45 files changed, 765 insertions(+), 317 deletions(-)
 delete mode 100644 src/main/java/com/ycwl/basic/config/OssConfig.java
 create mode 100644 src/main/java/com/ycwl/basic/controller/pc/FaceDetectLogController.java
 delete mode 100644 src/main/java/com/ycwl/basic/service/FileService.java
 create mode 100644 src/main/java/com/ycwl/basic/service/impl/pc/FaceDetectLogServiceImpl.java
 create mode 100644 src/main/java/com/ycwl/basic/service/pc/FaceDetectLogService.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/StorageFactory.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/adapters/AStorageAdapter.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/adapters/AliOssAdapter.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/adapters/IStorageAdapter.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/adapters/LocalStorageAdapter.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/entity/AliOssStorageConfig.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/entity/StorageConfig.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/enums/StorageType.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/exceptions/StorageConfigException.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/exceptions/StorageException.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/exceptions/StorageUndefinedException.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/exceptions/StorageUnsupportedException.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/exceptions/UploadFileFailedException.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/starter/StorageAutoConfiguration.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/starter/config/OverallStorageConfig.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/starter/config/StorageConfigItem.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/tests/TestInitializationSpeed.java
 create mode 100644 src/main/java/com/ycwl/basic/storage/utils/StorageUtil.java
 delete mode 100644 src/main/java/com/ycwl/basic/utils/OssUtil.java
 create mode 100644 src/main/java/com/ycwl/basic/videoTask/VideoTaskFactory.java
 create mode 100644 src/main/java/com/ycwl/basic/videoTask/adapters/DefaultAdapter.java
 create mode 100644 src/main/java/com/ycwl/basic/videoTask/adapters/IAdapter.java
 create mode 100644 src/main/java/com/ycwl/basic/videoTask/chains/VideoTaskGeneratorChain.java
 create mode 100644 src/main/java/com/ycwl/basic/videoTask/chains/interceptor/CheckFaceCountInterceptor.java
 create mode 100644 src/main/java/com/ycwl/basic/videoTask/chains/interceptor/IVidTaskGenChainInterceptor.java
 create mode 100644 src/main/java/com/ycwl/basic/videoTask/daos/TemplateDao.java
 create mode 100644 src/main/java/com/ycwl/basic/videoTask/entity/VideoTaskGeneratorContext.java
 create mode 100644 src/main/java/com/ycwl/basic/videoTask/enums/VideoTaskTriggerReason.java

diff --git a/src/main/java/com/ycwl/basic/config/FaceDetectConfig.java b/src/main/java/com/ycwl/basic/config/FaceDetectConfig.java
index b1f7dd3..42ed1c2 100644
--- a/src/main/java/com/ycwl/basic/config/FaceDetectConfig.java
+++ b/src/main/java/com/ycwl/basic/config/FaceDetectConfig.java
@@ -1,7 +1,12 @@
 package com.ycwl.basic.config;
 
+import com.ycwl.basic.storage.entity.AliOssStorageConfig;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
 import org.springframework.stereotype.Component;
 
 /**
@@ -12,10 +17,10 @@ import org.springframework.stereotype.Component;
 @Data
 @Component
 public class FaceDetectConfig {
-    @Value("${aliYunFace.accessKeyId}")
+    @Value("${aliFace.accessKeyId}")
     private String accessKeyId;
-    @Value("${aliYunFace.accessKeySecret}")
+    @Value("${aliFace.accessKeySecret}")
     private String accessKeySecret;
-    @Value("${aliYunFace.region}")
+    @Value("${aliFace.region}")
     private String region;
 }
diff --git a/src/main/java/com/ycwl/basic/config/OssConfig.java b/src/main/java/com/ycwl/basic/config/OssConfig.java
deleted file mode 100644
index 72d8b1a..0000000
--- a/src/main/java/com/ycwl/basic/config/OssConfig.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.ycwl.basic.config;
-
-import lombok.Data;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Component;
-
-/**
- * 阿里云OSS配置
- *
- * @author songmingsong
- **/
-@Data
-@Component
-public class OssConfig {
-    @Value("${aliYunOss.endpoint}")
-    private String endPoint;
-    @Value("${aliYunOss.accessKeyId}")
-    private String accessKeyId;
-    @Value("${aliYunOss.accessKeySecret}")
-    private String accessKeySecret;
-    @Value("${aliYunOss.bucketName}")
-    private String bucketName;
-    @Value("${aliYunOss.objectName}")
-    private String objectName;
-    @Value("${aliYunOss.url}")
-    private String url;
-    @Value("${aliYunOss.region}")
-    private String region;
-}
diff --git a/src/main/java/com/ycwl/basic/controller/FileController.java b/src/main/java/com/ycwl/basic/controller/FileController.java
index 9b7c5ed..dcdf9f0 100644
--- a/src/main/java/com/ycwl/basic/controller/FileController.java
+++ b/src/main/java/com/ycwl/basic/controller/FileController.java
@@ -3,7 +3,7 @@ package com.ycwl.basic.controller;
 
 import com.ycwl.basic.annotation.IgnoreToken;
 import com.ycwl.basic.enums.BizCodeEnum;
-import com.ycwl.basic.service.FileService;
+import com.ycwl.basic.storage.StorageFactory;
 import com.ycwl.basic.utils.ApiResponse;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.IOException;
+import java.util.UUID;
 
 /**
  * @Author: songmingsong
@@ -29,14 +30,13 @@ import java.io.IOException;
 @Api(tags = "文件接口")
 public class FileController {
 
-    @Autowired
-    private FileService fileService;
-
     @ApiOperation(value = "上传文件")
     @PostMapping("/upload")
     @IgnoreToken
     public ApiResponse<?> upload(@RequestParam(value = "file") MultipartFile file) throws IOException {
-        String url = fileService.uploadFile(file);
+        String[] split = file.getOriginalFilename().split("\\.");
+        String ext = split[split.length - 1];
+        String url = StorageFactory.use("assets").uploadFile(file, "web", UUID.randomUUID()+"."+ext);
         return ApiResponse.success(url);
     }
 
@@ -44,7 +44,7 @@ public class FileController {
     @PostMapping("/delete")
     @IgnoreToken
     public ApiResponse<?> delete(@RequestParam(value = "fileName") String fileName) throws IOException {
-        Boolean flag = fileService.delete(fileName);
+        boolean flag = StorageFactory.use("assets").deleteFile("web", fileName);
         return flag ? ApiResponse.success(BizCodeEnum.REQUEST_OK) : ApiResponse.fail(BizCodeEnum.FAIL.getMessage());
     }
 }
diff --git a/src/main/java/com/ycwl/basic/controller/pc/FaceDetectLogController.java b/src/main/java/com/ycwl/basic/controller/pc/FaceDetectLogController.java
new file mode 100644
index 0000000..847d66d
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/controller/pc/FaceDetectLogController.java
@@ -0,0 +1,30 @@
+package com.ycwl.basic.controller.pc;
+
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import com.ycwl.basic.model.common.BaseQueryParameterReq;
+import com.ycwl.basic.model.pc.faceDetectLog.entity.FaceDetectLog;
+import com.ycwl.basic.service.pc.FaceDetectLogService;
+import com.ycwl.basic.utils.ApiResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/face/detect_log/v1")
+public class FaceDetectLogController {
+    @Autowired
+    private FaceDetectLogService service;
+
+    @PostMapping("/page")
+    public ApiResponse<PageInfo<FaceDetectLog>> pageQuery(@RequestBody BaseQueryParameterReq req) {
+        PageHelper.startPage(req.getPageNum(), req.getPageSize());
+        List<FaceDetectLog> list = service.listByTimeDesc();
+        PageInfo<FaceDetectLog> pageInfo = new PageInfo<>(list);
+        return ApiResponse.success(pageInfo);
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/controller/viid/ViidController.java b/src/main/java/com/ycwl/basic/controller/viid/ViidController.java
index fab2fa4..23048f8 100644
--- a/src/main/java/com/ycwl/basic/controller/viid/ViidController.java
+++ b/src/main/java/com/ycwl/basic/controller/viid/ViidController.java
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
 import com.ycwl.basic.annotation.IgnoreToken;
+import com.ycwl.basic.config.FaceDetectConfig;
 import com.ycwl.basic.mapper.DeviceMapper;
 import com.ycwl.basic.mapper.FaceSampleMapper;
 import com.ycwl.basic.mapper.SourceMapper;
@@ -25,10 +26,11 @@ import com.ycwl.basic.model.viid.req.UnRegisterReq;
 import com.ycwl.basic.model.viid.resp.SystemTimeResp;
 import com.ycwl.basic.model.viid.resp.VIIDBaseResp;
 import com.ycwl.basic.service.task.TaskFaceService;
-import com.ycwl.basic.utils.AliFaceUtil;
+import com.ycwl.basic.storage.StorageFactory;
+import com.ycwl.basic.storage.adapters.IStorageAdapter;
+import com.ycwl.basic.storage.enums.StorageType;
 import com.ycwl.basic.utils.ImageUtils;
 import com.ycwl.basic.utils.IpUtils;
-import com.ycwl.basic.utils.OssUtil;
 import com.ycwl.basic.utils.SnowFlakeUtil;
 import io.swagger.annotations.Api;
 import lombok.extern.slf4j.Slf4j;
@@ -179,8 +181,6 @@ public class ViidController {
 
     @Autowired
     private TaskFaceService taskFaceService;
-    @Autowired
-    private OssUtil ossUtil;
 
 
     /**
@@ -223,6 +223,7 @@ public class ViidController {
                     if (ext.equalsIgnoreCase("jpeg")) {
                         ext = "jpg";
                     }
+                    IStorageAdapter adapter = StorageFactory.use("faces");
                     // Type=11 人脸
                     if (subImage.getType().equals("11")) {
                         // 上传oss
@@ -232,13 +233,7 @@ public class ViidController {
                         faceSample.setDeviceId(device.getId());
                         faceSample.setStatus(0);
                         faceSample.setCreateAt(new Date());
-                        String url;
-                        try {
-                            url = ossUtil.uploadFile(file.getInputStream(), UUID.randomUUID().toString() + "." + ext);
-                        } catch (IOException e) {
-                            log.error("文件上传失败!", e);
-                            continue;
-                        }
+                        String url = adapter.uploadFile(file, "user-face", UUID.randomUUID() + "." + ext);
                         faceSample.setFaceUrl(url);
                         faceSampleMapper.add(faceSample);
                         log.info("人脸信息入库成功!");
@@ -255,13 +250,7 @@ public class ViidController {
                         source.setFaceSampleId(newFaceSampleId);
                         source.setType(2);
                         // 上传oss
-                        String url;
-                        try {
-                            url = ossUtil.uploadFile(file.getInputStream(), "user-photo/", newFaceSampleId + "." + ext);
-                        } catch (IOException e) {
-                            log.error("文件上传失败!", e);
-                            continue;
-                        }
+                        String url = adapter.uploadFile(file, "user-photo", UUID.randomUUID() + "." + ext);
                         source.setUrl(url);
                         source.setPosJson(JSON.toJSONString(facePosition));
                         sourceMapper.add(source);
diff --git a/src/main/java/com/ycwl/basic/model/pc/template/entity/TemplateEntity.java b/src/main/java/com/ycwl/basic/model/pc/template/entity/TemplateEntity.java
index ac40718..9c06719 100644
--- a/src/main/java/com/ycwl/basic/model/pc/template/entity/TemplateEntity.java
+++ b/src/main/java/com/ycwl/basic/model/pc/template/entity/TemplateEntity.java
@@ -29,7 +29,7 @@ public class TemplateEntity {
     /**
      * 父模版ID
      */
-    private Long pid;
+    private long pid = 0;
     /**
      * 是否是占位素材,0不是,1是
      */
diff --git a/src/main/java/com/ycwl/basic/service/FileService.java b/src/main/java/com/ycwl/basic/service/FileService.java
deleted file mode 100644
index 361db86..0000000
--- a/src/main/java/com/ycwl/basic/service/FileService.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.ycwl.basic.service;
-
-import cn.hutool.core.util.StrUtil;
-import com.ycwl.basic.exception.BaseException;
-import com.ycwl.basic.utils.OssUtil;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.web.multipart.MultipartFile;
-
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * file请求服务
- *
- * @author songmingsong
- */
-@Service
-public class FileService {
-
-    @Autowired
-    private OssUtil ossUtil;
-
-    public String uploadFile(MultipartFile file) throws IOException {
-        String originalFilename = file.getOriginalFilename();
-        if (StrUtil.isBlank(originalFilename)) {
-            throw new BaseException("文件上传失败,文件名不能为空");
-        }
-        String fileName=System.currentTimeMillis() + originalFilename.substring(originalFilename.lastIndexOf("."));
-        return ossUtil.uploadFile(file.getInputStream(), fileName);
-    }
-
-    public Boolean delete(String fileName) {
-        return ossUtil.deleteFile(fileName);
-    }
-}
diff --git a/src/main/java/com/ycwl/basic/service/impl/pc/FaceDetectLogServiceImpl.java b/src/main/java/com/ycwl/basic/service/impl/pc/FaceDetectLogServiceImpl.java
new file mode 100644
index 0000000..55e7165
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/service/impl/pc/FaceDetectLogServiceImpl.java
@@ -0,0 +1,22 @@
+package com.ycwl.basic.service.impl.pc;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ycwl.basic.mapper.FaceDetectLogMapper;
+import com.ycwl.basic.model.pc.faceDetectLog.entity.FaceDetectLog;
+import com.ycwl.basic.service.pc.FaceDetectLogService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+
+@Service
+public class FaceDetectLogServiceImpl extends ServiceImpl<FaceDetectLogMapper, FaceDetectLog> implements FaceDetectLogService {
+    @Override
+    public List<FaceDetectLog> listByTimeDesc() {
+        LambdaQueryWrapper<FaceDetectLog> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.orderByDesc(FaceDetectLog::getCreateTime);
+        return list(queryWrapper);
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/service/impl/pc/FaceServiceImpl.java b/src/main/java/com/ycwl/basic/service/impl/pc/FaceServiceImpl.java
index 1e0df48..cfcb729 100644
--- a/src/main/java/com/ycwl/basic/service/impl/pc/FaceServiceImpl.java
+++ b/src/main/java/com/ycwl/basic/service/impl/pc/FaceServiceImpl.java
@@ -16,6 +16,10 @@ import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
 import com.ycwl.basic.service.pc.FaceService;
 import com.ycwl.basic.service.task.TaskFaceService;
 import com.ycwl.basic.service.task.TaskService;
+import com.ycwl.basic.storage.StorageFactory;
+import com.ycwl.basic.storage.adapters.IStorageAdapter;
+import com.ycwl.basic.storage.enums.StorageType;
+import com.ycwl.basic.storage.utils.StorageUtil;
 import com.ycwl.basic.task.FaceCleaner;
 import com.ycwl.basic.utils.*;
 import lombok.extern.slf4j.Slf4j;
@@ -31,6 +35,7 @@ import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
 import java.util.Objects;
+import java.util.UUID;
 import java.util.stream.Collectors;
 
 import static com.ycwl.basic.constant.FaceConstant.USER_FACE_DB_NAME;
@@ -45,8 +50,6 @@ public class FaceServiceImpl implements FaceService {
     @Autowired
     private FaceMapper faceMapper;
     @Autowired
-    private OssUtil ossUtil;
-    @Autowired
     private TaskFaceService faceService;
     @Autowired
     private StatisticsMapper statisticsMapper;
@@ -56,8 +59,6 @@ public class FaceServiceImpl implements FaceService {
     private float strictScore = 90F;
     @Autowired
     private TaskService taskTaskService;
-    @Autowired
-    private FaceCleaner faceCleaner;
 
     @Override
     public ApiResponse<PageInfo<FaceRespVO>> pageQuery(FaceReqQuery faceReqQuery) {
@@ -123,17 +124,21 @@ public class FaceServiceImpl implements FaceService {
         log.info("当前登录用户信息:{}",worker);
 
         //1、上传人脸照片
-        String faceUrl = uploadFileALiOss(file, userId);
+        IStorageAdapter adapter = StorageFactory.use("faces");
+        String filePath = StorageUtil.joinPath("user-faces", DateUtils.format(new Date(),"yyyy-MM-dd"));
+        String originalFilename = file.getOriginalFilename();
+        String suffix = originalFilename.split("\\.", 2)[1];
+        String fileName = UUID.randomUUID().toString() + "." + suffix;
+        String faceUrl = adapter.uploadFile(file, filePath, fileName);
         SearchFaceRespVo scenicDbSearchResult = faceService.searchFace(scenicId, faceUrl);
         if (scenicDbSearchResult == null) {
-            ossUtil.deleteFileByUrl(faceUrl);
+            adapter.deleteFile(filePath, fileName);
             throw new BaseException("人脸照片校验失败,请重新上传");
         }
         float score = scenicDbSearchResult.getScore();
         if (score<faceScore) {
             //校验失败,删除,提示重新上传
-            ossUtil.deleteFileByUrl(faceUrl);
-
+            adapter.deleteFile(filePath, fileName);
             throw new BaseException("人脸照片校验失败,请重新上传");
         }
         // 2、通过人脸查找用户库
@@ -194,37 +199,4 @@ public class FaceServiceImpl implements FaceService {
         return ApiResponse.success(faceMapper.getByMemberId(memberId, 3928516560393736192L));
     }
 
-    /**
-     * 阿里oss图片上传
-     *
-     * @param file file
-     * @param userId 用户id
-     * @return 地址
-     */
-    private String uploadFileALiOss(MultipartFile file,Long userId)  {
-        if (file.isEmpty()) {
-            throw new RuntimeException("文件不存在!");
-        }
-        String originalFilename = file.getOriginalFilename();
-        //获取文件名后缀
-        String suffix = originalFilename.split("\\.")[1];
-        if ("Jpeg".equals(suffix)) {
-            suffix = "jpg";
-        }
-        //文件储存路径
-        String filePath="";
-        String dateStr = DateUtils.format(new Date(),"yyyy-MM-dd");
-
-        filePath=filePath+dateStr+"/";
-        // 生成文件名
-        String fileName= userId+"." + suffix;
-        InputStream inputStream ;
-        try {
-            inputStream = file.getInputStream();
-        } catch (IOException e) {
-            log.error("文件上传失败!", e);
-            return null;
-        }
-        return ossUtil.uploadFile(inputStream,filePath+fileName) ;
-    }
 }
diff --git a/src/main/java/com/ycwl/basic/service/impl/task/TaskFaceServiceImpl.java b/src/main/java/com/ycwl/basic/service/impl/task/TaskFaceServiceImpl.java
index 7bdaa54..9297ba3 100644
--- a/src/main/java/com/ycwl/basic/service/impl/task/TaskFaceServiceImpl.java
+++ b/src/main/java/com/ycwl/basic/service/impl/task/TaskFaceServiceImpl.java
@@ -33,6 +33,11 @@ import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
 import com.ycwl.basic.model.task.resp.AddFaceSampleRespVo;
 import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
 import com.ycwl.basic.service.task.TaskFaceService;
+import com.ycwl.basic.storage.StorageFactory;
+import com.ycwl.basic.storage.adapters.IStorageAdapter;
+import com.ycwl.basic.storage.enums.StorageType;
+import com.ycwl.basic.storage.utils.StorageUtil;
+import com.ycwl.basic.utils.DateUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -41,7 +46,10 @@ import org.springframework.stereotype.Service;
 import com.aliyuncs.DefaultAcsClient;
 import com.aliyuncs.IAcsClient;
 import com.aliyuncs.profile.DefaultProfile;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -117,9 +125,7 @@ public class TaskFaceServiceImpl implements TaskFaceService {
             if (matchList.get(0).getFaceItems().isEmpty()) {
                 return respVo;
             }
-            List<SearchFaceResponse.Data.MatchListItem.FaceItemsItem> faceItems = matchList.get(0).getFaceItems().stream()
-                    .filter(faceItemsItem -> faceItemsItem.getConfidence() > 50).collect(Collectors.toList());
-            List<MatchLocalRecord> records = faceItems.stream()
+            List<MatchLocalRecord> records = matchList.get(0).getFaceItems().stream()
                     .map(item -> {
                         MatchLocalRecord record = new MatchLocalRecord();
                         record.setIdStr(item.getExtraData());
@@ -135,6 +141,7 @@ public class TaskFaceServiceImpl implements TaskFaceService {
                     .collect(Collectors.toList());
             log.matchLocalRecord(records);
             List<Long> faceSampleIds = records.stream()
+                    .filter(record -> record.getConfidence() > 60)
                     .map(MatchLocalRecord::getFaceSampleId)
                     .collect(Collectors.toList());
             respVo.setFirstMatchRate(matchList.get(0).getFaceItems().get(0).getConfidence());
@@ -310,6 +317,30 @@ public class TaskFaceServiceImpl implements TaskFaceService {
         }
     }
 
+    @Override
+    public String uploadFile(MultipartFile file, Long userId) {
+        if (file.isEmpty()) {
+            throw new RuntimeException("文件不存在!");
+        }
+        String originalFilename = file.getOriginalFilename();
+        //获取文件名后缀
+        String suffix = originalFilename.split("\\.")[1];
+        if ("Jpeg".equals(suffix)) {
+            suffix = "jpg";
+        }
+        //文件储存路径
+        String filePath = StorageUtil.joinPath("user-faces", DateUtils.format(new Date(),"yyyy-MM-dd"));
+        // 生成文件名
+        String fileName= userId + "." + suffix;
+        IStorageAdapter adapter = StorageFactory.use("faces");
+        try {
+            return adapter.uploadFile(file.getInputStream(), filePath, fileName);
+        } catch (IOException e) {
+            log.error("文件上传失败!", e);
+            return null;
+        }
+    }
+
     public void addFaceDBCache(String dbName) {
         redisTemplate.opsForValue().set(FaceConstant.FACE_DB_NAME_PFX + dbName, "1");
     }
diff --git a/src/main/java/com/ycwl/basic/service/impl/task/TaskTaskServiceImpl.java b/src/main/java/com/ycwl/basic/service/impl/task/TaskTaskServiceImpl.java
index 6e2f893..b90790b 100644
--- a/src/main/java/com/ycwl/basic/service/impl/task/TaskTaskServiceImpl.java
+++ b/src/main/java/com/ycwl/basic/service/impl/task/TaskTaskServiceImpl.java
@@ -2,11 +2,6 @@ package com.ycwl.basic.service.impl.task;
 
 import com.alibaba.fastjson.JSON;
 import com.ycwl.basic.constant.TaskConstant;
-import com.ycwl.basic.device.DeviceFactory;
-import com.ycwl.basic.device.operator.IDeviceStorageOperator;
-import com.ycwl.basic.mapper.DeviceMapper;
-import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity;
-import com.ycwl.basic.model.pc.device.entity.DeviceEntity;
 import com.ycwl.basic.mapper.FaceMapper;
 import com.ycwl.basic.mapper.FaceSampleMapper;
 import com.ycwl.basic.mapper.RenderWorkerMapper;
@@ -31,8 +26,10 @@ import com.ycwl.basic.model.task.req.TaskReqVo;
 import com.ycwl.basic.model.task.req.WorkerAuthReqVo;
 import com.ycwl.basic.model.task.resp.TaskSyncRespVo;
 import com.ycwl.basic.service.task.TaskService;
+import com.ycwl.basic.storage.StorageFactory;
+import com.ycwl.basic.storage.adapters.IStorageAdapter;
+import com.ycwl.basic.storage.enums.StorageType;
 import com.ycwl.basic.task.VideoPieceGetter;
-import com.ycwl.basic.utils.OssUtil;
 import com.ycwl.basic.utils.SnowFlakeUtil;
 import lombok.NonNull;
 import org.apache.commons.lang3.StringUtils;
@@ -68,8 +65,6 @@ public class TaskTaskServiceImpl implements TaskService {
     @Autowired
     private SourceMapper sourceMapper;
     @Autowired
-    private OssUtil ossUtil;
-    @Autowired
     private VideoMapper videoMapper;
 
     private RenderWorkerEntity getWorker(@NonNull WorkerAuthReqVo req) {
@@ -334,15 +329,16 @@ public class TaskTaskServiceImpl implements TaskService {
         if (task == null) {
             return null;
         }
+        IStorageAdapter adapter = StorageFactory.use("faces");
         String filename = task.getId() + "_" + task.getScenicId() + ".mp4";
         if (StringUtils.isBlank(task.getVideoUrl())) {
             // 生成
-            String url = ossUtil.generateUrlOfFile("user-video/", filename);
+            String url = adapter.getUrl("user-video", filename);
             TaskEntity updateTask = new TaskEntity();
             updateTask.setId(taskId);
             updateTask.setVideoUrl(url);
             taskMapper.update(updateTask);
         }
-        return ossUtil.generateSignedUrlForUpload("user-video/", filename);
+        return adapter.getUrlForUpload("user-video", filename);
     }
 }
diff --git a/src/main/java/com/ycwl/basic/service/pc/FaceDetectLogService.java b/src/main/java/com/ycwl/basic/service/pc/FaceDetectLogService.java
new file mode 100644
index 0000000..35a5f60
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/service/pc/FaceDetectLogService.java
@@ -0,0 +1,10 @@
+package com.ycwl.basic.service.pc;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ycwl.basic.model.pc.faceDetectLog.entity.FaceDetectLog;
+
+import java.util.List;
+
+public interface FaceDetectLogService extends IService<FaceDetectLog> {
+    List<FaceDetectLog> listByTimeDesc();
+}
diff --git a/src/main/java/com/ycwl/basic/service/task/TaskFaceService.java b/src/main/java/com/ycwl/basic/service/task/TaskFaceService.java
index c753a1f..a364ab2 100644
--- a/src/main/java/com/ycwl/basic/service/task/TaskFaceService.java
+++ b/src/main/java/com/ycwl/basic/service/task/TaskFaceService.java
@@ -2,6 +2,7 @@ package com.ycwl.basic.service.task;
 
 import com.ycwl.basic.model.task.resp.AddFaceSampleRespVo;
 import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
+import org.springframework.web.multipart.MultipartFile;
 
 public interface TaskFaceService {
 
@@ -22,4 +23,6 @@ public interface TaskFaceService {
     void createFaceDB(String scenicId);
 
     void assureFaceDB(String scenicId);
+
+    String uploadFile(MultipartFile file, Long userId);
 }
diff --git a/src/main/java/com/ycwl/basic/storage/StorageFactory.java b/src/main/java/com/ycwl/basic/storage/StorageFactory.java
new file mode 100644
index 0000000..4cc28e2
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/StorageFactory.java
@@ -0,0 +1,53 @@
+package com.ycwl.basic.storage;
+
+import com.ycwl.basic.storage.adapters.AliOssAdapter;
+import com.ycwl.basic.storage.adapters.IStorageAdapter;
+import com.ycwl.basic.storage.adapters.LocalStorageAdapter;
+import com.ycwl.basic.storage.entity.StorageConfig;
+import com.ycwl.basic.storage.enums.StorageType;
+import com.ycwl.basic.storage.exceptions.StorageUndefinedException;
+import com.ycwl.basic.storage.exceptions.StorageUnsupportedException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class StorageFactory {
+    public static IStorageAdapter get(StorageType storageType, StorageConfig config) {
+        IStorageAdapter adapter = get(storageType);
+        adapter.setConfig(config);
+        return adapter;
+    }
+
+    public static IStorageAdapter get(StorageType storageType) {
+        switch (storageType) {
+            case LOCAL:
+                return new LocalStorageAdapter();
+            case ALI_OSS:
+                return new AliOssAdapter();
+            default:
+                throw new StorageUnsupportedException(storageType.getType());
+        }
+    }
+
+    public static IStorageAdapter get(String type) {
+        StorageType storageType = StorageType.getStorageType(type);
+        if (storageType == null) {
+            throw new StorageUnsupportedException(type);
+        }
+        return get(storageType);
+    }
+
+    public static Map<String, IStorageAdapter> definedName = new HashMap<>();
+
+    public static void register(String name, IStorageAdapter adapter) {
+        definedName.put(name, adapter);
+    }
+
+    public static IStorageAdapter use(String name) {
+        IStorageAdapter adapter = definedName.get(name);
+        if (adapter == null) {
+            throw new StorageUndefinedException(name);
+        }
+        return adapter;
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/storage/adapters/AStorageAdapter.java b/src/main/java/com/ycwl/basic/storage/adapters/AStorageAdapter.java
new file mode 100644
index 0000000..7bb87f1
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/adapters/AStorageAdapter.java
@@ -0,0 +1,46 @@
+package com.ycwl.basic.storage.adapters;
+
+import com.ycwl.basic.storage.entity.StorageConfig;
+import com.ycwl.basic.storage.exceptions.UploadFileFailedException;
+import lombok.Setter;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.Date;
+
+public abstract class AStorageAdapter implements IStorageAdapter {
+
+    @Override
+    public String uploadFile(File file, String path, String filename) {
+        if (file == null) {
+            return null;
+        }
+        try {
+            InputStream inputStream = new FileInputStream(file);
+            return uploadFile(inputStream, path, filename);
+        } catch (FileNotFoundException e) {
+            throw new UploadFileFailedException("文件不存在");
+        }
+    }
+
+    @Override
+    public String uploadFile(MultipartFile file, String path, String filename) {
+        if (file == null) {
+            return null;
+        }
+        try {
+            InputStream inputStream = file.getInputStream();
+            return uploadFile(inputStream, path, filename);
+        } catch (Exception e) {
+            throw new UploadFileFailedException("文件上传失败");
+        }
+    }
+
+    @Override
+    public String getUrlForUpload(String path, String filename) {
+        return getUrlForUpload(path, filename, new Date(System.currentTimeMillis() + 1000 * 60 * 60));
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/storage/adapters/AliOssAdapter.java b/src/main/java/com/ycwl/basic/storage/adapters/AliOssAdapter.java
new file mode 100644
index 0000000..6664dd6
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/adapters/AliOssAdapter.java
@@ -0,0 +1,102 @@
+package com.ycwl.basic.storage.adapters;
+
+
+import com.aliyun.oss.ClientException;
+import com.aliyun.oss.HttpMethod;
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.model.PutObjectRequest;
+import com.ycwl.basic.storage.entity.AliOssStorageConfig;
+import com.ycwl.basic.storage.entity.StorageConfig;
+import com.ycwl.basic.storage.exceptions.StorageConfigException;
+import com.ycwl.basic.storage.exceptions.UploadFileFailedException;
+import com.ycwl.basic.storage.utils.StorageUtil;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Date;
+import java.util.Map;
+
+public class AliOssAdapter extends AStorageAdapter {
+    private AliOssStorageConfig config;
+
+    @Override
+    public void loadConfig(Map<String, String> _config) {
+        AliOssStorageConfig config = new AliOssStorageConfig();
+        config.setAccessKeyId(_config.get("accessKeyId"));
+        config.setAccessKeySecret(_config.get("accessKeySecret"));
+        config.setBucketName(_config.get("bucketName"));
+        config.setEndpoint(_config.get("endpoint"));
+        config.setRegion(_config.get("region"));
+        config.setUrl(_config.get("url"));
+        config.setPrefix(_config.get("prefix"));
+        config.checkEverythingOK();
+        this.config = config;
+    }
+
+    @Override
+    public void setConfig(StorageConfig config) {
+        if (config == null) {
+            throw new StorageConfigException("配置为空");
+        }
+        if (config instanceof AliOssStorageConfig) {
+            this.config = (AliOssStorageConfig) config;
+        } else {
+            throw new StorageConfigException("配置类型错误,传入的类为:" + config.getClass().getName());
+        }
+    }
+
+    @Override
+    public String uploadFile(InputStream inputStream, String path, String filename) {
+        if (inputStream == null) {
+            return null;
+        }
+        String fullPath = buildPath(path, filename);
+        OSS ossClient = getOssClient();
+        try {
+            PutObjectRequest putObjectRequest = new PutObjectRequest(config.getBucketName(), fullPath, inputStream);
+            ossClient.putObject(putObjectRequest);
+            return getUrl(path, filename);
+        } catch (ClientException e) {
+            throw new UploadFileFailedException("上传文件失败:" + e.getErrorMessage());
+        }
+    }
+
+
+    @Override
+    public boolean deleteFile(String path, String filename) {
+        OSS ossClient = getOssClient();
+        try {
+            ossClient.deleteObject(config.getBucketName(), buildPath(path, filename));
+            return true;
+        } catch (ClientException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public String getUrl(String path, String filename) {
+        return config.getUrl() + buildPath(path, filename);
+    }
+
+    @Override
+    public String getUrlForUpload(String path, String filename, Date expireDate) {
+        OSS ossClient = getOssClient();
+        URL url = ossClient.generatePresignedUrl(config.getBucketName(), buildPath(path, filename), expireDate, HttpMethod.PUT);
+        return url.toString();
+    }
+
+    private OSS getOssClient() {
+        OSS ossClient = new OSSClientBuilder().build(config.getEndpoint(), config.getAccessKeyId(), config.getAccessKeySecret());
+        return ossClient;
+    }
+
+    private String buildPath(String ...paths) {
+        if (StringUtils.isNotBlank(config.getPrefix())) {
+            return StorageUtil.joinPath(config.getPrefix(), paths);
+        } else {
+            return StorageUtil.joinPath(paths);
+        }
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/storage/adapters/IStorageAdapter.java b/src/main/java/com/ycwl/basic/storage/adapters/IStorageAdapter.java
new file mode 100644
index 0000000..0258f11
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/adapters/IStorageAdapter.java
@@ -0,0 +1,21 @@
+package com.ycwl.basic.storage.adapters;
+
+import com.ycwl.basic.storage.entity.StorageConfig;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Date;
+import java.util.Map;
+
+public interface IStorageAdapter {
+    void loadConfig(Map<String, String> config);
+    void setConfig(StorageConfig config);
+    String uploadFile(InputStream inputStream, String path, String filename);
+    String uploadFile(File file, String path, String filename);
+    String uploadFile(MultipartFile file, String path, String filename);
+    boolean deleteFile(String path, String filename);
+    String getUrl(String path, String filename);
+    String getUrlForUpload(String path, String filename);
+    String getUrlForUpload(String path, String filename, Date expireDate);
+}
diff --git a/src/main/java/com/ycwl/basic/storage/adapters/LocalStorageAdapter.java b/src/main/java/com/ycwl/basic/storage/adapters/LocalStorageAdapter.java
new file mode 100644
index 0000000..f484712
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/adapters/LocalStorageAdapter.java
@@ -0,0 +1,39 @@
+package com.ycwl.basic.storage.adapters;
+
+import com.ycwl.basic.storage.entity.StorageConfig;
+
+import java.io.InputStream;
+import java.util.Date;
+import java.util.Map;
+
+public class LocalStorageAdapter extends AStorageAdapter{
+    @Override
+    public void loadConfig(Map<String, String> config) {
+
+    }
+
+    @Override
+    public void setConfig(StorageConfig config) {
+
+    }
+
+    @Override
+    public String uploadFile(InputStream inputStream, String path, String filename) {
+        return "";
+    }
+
+    @Override
+    public boolean deleteFile(String path, String filename) {
+        return false;
+    }
+
+    @Override
+    public String getUrl(String path, String filename) {
+        return "";
+    }
+
+    @Override
+    public String getUrlForUpload(String path, String filename, Date expireDate) {
+        return "";
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/storage/entity/AliOssStorageConfig.java b/src/main/java/com/ycwl/basic/storage/entity/AliOssStorageConfig.java
new file mode 100644
index 0000000..34d0ad0
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/entity/AliOssStorageConfig.java
@@ -0,0 +1,21 @@
+package com.ycwl.basic.storage.entity;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class AliOssStorageConfig extends StorageConfig {
+    private String endpoint;
+    private String accessKeyId;
+    private String accessKeySecret;
+    private String bucketName;
+    private String url;
+    private String region;
+    private String prefix;
+
+    @Override
+    public void checkEverythingOK() {
+        // TODO: 检查配置是否正确
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/storage/entity/StorageConfig.java b/src/main/java/com/ycwl/basic/storage/entity/StorageConfig.java
new file mode 100644
index 0000000..96b16eb
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/entity/StorageConfig.java
@@ -0,0 +1,5 @@
+package com.ycwl.basic.storage.entity;
+
+public abstract class StorageConfig {
+    public abstract void checkEverythingOK();
+}
diff --git a/src/main/java/com/ycwl/basic/storage/enums/StorageType.java b/src/main/java/com/ycwl/basic/storage/enums/StorageType.java
new file mode 100644
index 0000000..3f5d9fd
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/enums/StorageType.java
@@ -0,0 +1,27 @@
+package com.ycwl.basic.storage.enums;
+
+import com.ycwl.basic.storage.adapters.AliOssAdapter;
+import com.ycwl.basic.storage.adapters.IStorageAdapter;
+import com.ycwl.basic.storage.adapters.LocalStorageAdapter;
+import lombok.Getter;
+
+public enum StorageType {
+    LOCAL("LOCAL"),
+    ALI_OSS("ALI_OSS");
+
+    @Getter
+    private final String type;
+
+    StorageType(String type) {
+        this.type = type;
+    }
+
+    public static StorageType getStorageType(String type) {
+        for (StorageType storageType : StorageType.values()) {
+            if (storageType.getType().equals(type)) {
+                return storageType;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/storage/exceptions/StorageConfigException.java b/src/main/java/com/ycwl/basic/storage/exceptions/StorageConfigException.java
new file mode 100644
index 0000000..c913591
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/exceptions/StorageConfigException.java
@@ -0,0 +1,7 @@
+package com.ycwl.basic.storage.exceptions;
+
+public class StorageConfigException extends StorageException {
+    public StorageConfigException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/storage/exceptions/StorageException.java b/src/main/java/com/ycwl/basic/storage/exceptions/StorageException.java
new file mode 100644
index 0000000..d38b50d
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/exceptions/StorageException.java
@@ -0,0 +1,7 @@
+package com.ycwl.basic.storage.exceptions;
+
+public class StorageException extends RuntimeException {
+    public StorageException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/storage/exceptions/StorageUndefinedException.java b/src/main/java/com/ycwl/basic/storage/exceptions/StorageUndefinedException.java
new file mode 100644
index 0000000..ff40037
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/exceptions/StorageUndefinedException.java
@@ -0,0 +1,7 @@
+package com.ycwl.basic.storage.exceptions;
+
+public class StorageUndefinedException extends StorageException {
+    public StorageUndefinedException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/storage/exceptions/StorageUnsupportedException.java b/src/main/java/com/ycwl/basic/storage/exceptions/StorageUnsupportedException.java
new file mode 100644
index 0000000..a962cda
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/exceptions/StorageUnsupportedException.java
@@ -0,0 +1,7 @@
+package com.ycwl.basic.storage.exceptions;
+
+public class StorageUnsupportedException extends StorageException {
+    public StorageUnsupportedException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/storage/exceptions/UploadFileFailedException.java b/src/main/java/com/ycwl/basic/storage/exceptions/UploadFileFailedException.java
new file mode 100644
index 0000000..fb99424
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/exceptions/UploadFileFailedException.java
@@ -0,0 +1,7 @@
+package com.ycwl.basic.storage.exceptions;
+
+public class UploadFileFailedException extends StorageException {
+    public UploadFileFailedException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/storage/starter/StorageAutoConfiguration.java b/src/main/java/com/ycwl/basic/storage/starter/StorageAutoConfiguration.java
new file mode 100644
index 0000000..534e7a7
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/starter/StorageAutoConfiguration.java
@@ -0,0 +1,30 @@
+package com.ycwl.basic.storage.starter;
+
+import com.ycwl.basic.storage.StorageFactory;
+import com.ycwl.basic.storage.adapters.IStorageAdapter;
+import com.ycwl.basic.storage.starter.config.OverallStorageConfig;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Map;
+
+@Configuration
+public class StorageAutoConfiguration {
+    private final OverallStorageConfig config;
+    public StorageAutoConfiguration(OverallStorageConfig config) {
+        this.config = config;
+        if (config != null) {
+            loadConfig();
+        }
+    }
+
+    private void loadConfig() {
+        config.getConfigs().forEach(item -> {
+            String name = item.getName();
+            String type = item.getType();
+            IStorageAdapter adapter = StorageFactory.get(type);
+            Map<String, String> config = item.getConfig();
+            adapter.loadConfig(config);
+            StorageFactory.register(name, adapter);
+        });
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/storage/starter/config/OverallStorageConfig.java b/src/main/java/com/ycwl/basic/storage/starter/config/OverallStorageConfig.java
new file mode 100644
index 0000000..ea1d2fe
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/starter/config/OverallStorageConfig.java
@@ -0,0 +1,15 @@
+package com.ycwl.basic.storage.starter.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+@Component
+@ConfigurationProperties(prefix = "storage")
+@Data
+public class OverallStorageConfig {
+    private List<StorageConfigItem> configs;
+}
diff --git a/src/main/java/com/ycwl/basic/storage/starter/config/StorageConfigItem.java b/src/main/java/com/ycwl/basic/storage/starter/config/StorageConfigItem.java
new file mode 100644
index 0000000..745b14f
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/starter/config/StorageConfigItem.java
@@ -0,0 +1,12 @@
+package com.ycwl.basic.storage.starter.config;
+
+import lombok.Data;
+
+import java.util.Map;
+
+@Data
+public class StorageConfigItem {
+    private String name;
+    private String type;
+    private Map<String, String> config;
+}
diff --git a/src/main/java/com/ycwl/basic/storage/tests/TestInitializationSpeed.java b/src/main/java/com/ycwl/basic/storage/tests/TestInitializationSpeed.java
new file mode 100644
index 0000000..35e7c50
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/tests/TestInitializationSpeed.java
@@ -0,0 +1,19 @@
+package com.ycwl.basic.storage.tests;
+
+import com.ycwl.basic.storage.StorageFactory;
+import com.ycwl.basic.storage.adapters.AliOssAdapter;
+import com.ycwl.basic.storage.adapters.IStorageAdapter;
+import com.ycwl.basic.storage.enums.StorageType;
+
+public class TestInitializationSpeed {
+    public static void main(String[] args) {
+        int i = 0;
+        long currentTimestamp = System.currentTimeMillis();
+        System.out.println("开始测试");
+        while (i++ < 100000000) {
+//            IStorageAdapter adapter = new AliOssAdapter();
+            StorageFactory.get(StorageType.ALI_OSS);
+        }
+        System.out.println("结束测试,耗时:" + (System.currentTimeMillis() - currentTimestamp) + "ms");
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/storage/utils/StorageUtil.java b/src/main/java/com/ycwl/basic/storage/utils/StorageUtil.java
new file mode 100644
index 0000000..bfc5a9f
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/storage/utils/StorageUtil.java
@@ -0,0 +1,41 @@
+package com.ycwl.basic.storage.utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+public class StorageUtil {
+    public static String joinPath(String path, String subPath) {
+        if (StringUtils.isBlank(path)) {
+            return subPath;
+        }
+        if (subPath.startsWith("/")) {
+            subPath = subPath.substring(1);
+        }
+        if (path.endsWith("/")) {
+            path = path.substring(0, path.length() - 1);
+        }
+        if (StringUtils.isBlank(subPath)) {
+            return path;
+        }
+        if (path.endsWith("/")) {
+            return path + subPath;
+        } else {
+            return path + "/" + subPath;
+        }
+    }
+
+    public static String joinPath(String ...names) {
+        String name = names[0];
+        for (int i = 1; i < names.length; i++) {
+            name = joinPath(name, names[i]);
+        }
+        return name;
+    }
+
+
+    public static String joinPath(String name, String ...names) {
+        for (String s : names) {
+            name = joinPath(name, s);
+        }
+        return name;
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/task/DynamicTaskGenerator.java b/src/main/java/com/ycwl/basic/task/DynamicTaskGenerator.java
index 1ec78de..20ad866 100644
--- a/src/main/java/com/ycwl/basic/task/DynamicTaskGenerator.java
+++ b/src/main/java/com/ycwl/basic/task/DynamicTaskGenerator.java
@@ -78,6 +78,7 @@ public class DynamicTaskGenerator {
                     }
                 }
                 Integer minimalPlaceholderFill = templateConfig.getMinimalPlaceholderFill();
+                int maxPlaceholder = 0;
                 List<String> placeholderList = new ArrayList<>();
                 if (minimalPlaceholderFill == null) {
                     minimalPlaceholderFill = 0;
@@ -90,6 +91,7 @@ public class DynamicTaskGenerator {
                 }
                 if (minimalPlaceholderFill == 0) {
                     for (TemplateRespVO subTemplate : subTemplateList) {
+                        maxPlaceholder += 1;
                         if (subTemplate.getIsPlaceholder() == 1) {
                             minimalPlaceholderFill += 1;
                         }
@@ -127,7 +129,11 @@ public class DynamicTaskGenerator {
                                 matchedPlaceholder += 1;
                             }
                         }
-                        if (matchedPlaceholder >= minimalPlaceholderFill) {
+                        if (matchedPlaceholder >= maxPlaceholder) {
+                            log.info("当前人脸样本{}已超过最大占位素材{},自动创建任务", face.getFaceUrl(), maxPlaceholder);
+                            taskService.createRenderTask(scenic.getId(), template.getId(), face.getId());
+                            faceMapper.finishedJourney(face.getId());
+                        } else if (matchedPlaceholder >= minimalPlaceholderFill) {
                             log.info("当前人脸样本{}已超过最小占位素材{},自动创建任务", face.getFaceUrl(), minimalPlaceholderFill);
                             taskService.createRenderTask(scenic.getId(), template.getId(), face.getId());
                             faceMapper.finishedJourney(face.getId());
diff --git a/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java b/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java
index 0ced63c..dd0f8bf 100644
--- a/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java
+++ b/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java
@@ -10,7 +10,9 @@ import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity;
 import com.ycwl.basic.model.pc.device.entity.DeviceEntity;
 import com.ycwl.basic.model.pc.faceSample.resp.FaceSampleRespVO;
 import com.ycwl.basic.model.pc.source.entity.SourceEntity;
-import com.ycwl.basic.utils.OssUtil;
+import com.ycwl.basic.storage.StorageFactory;
+import com.ycwl.basic.storage.adapters.IStorageAdapter;
+import com.ycwl.basic.storage.enums.StorageType;
 import com.ycwl.basic.utils.SnowFlakeUtil;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
@@ -44,8 +46,6 @@ public class VideoPieceGetter {
     @Autowired
     private DeviceMapper deviceMapper;
     @Autowired
-    private OssUtil ossUtil;
-    @Autowired
     private SourceMapper sourceMapper;
 
     @Data
@@ -123,27 +123,23 @@ public class VideoPieceGetter {
             return;
         }
         log.info("视频裁切成功");
-        try {
-            InputStream inputStream = new FileInputStream(outFile);
-            String url = ossUtil.uploadFile(inputStream, "user-video-source/", outFile.getName());
-            SourceEntity imgSource = sourceMapper.findBySampleId(faceSample.getId());
-            SourceEntity sourceEntity = new SourceEntity();
-            sourceEntity.setId(SnowFlakeUtil.getLongId());
-            if (imgSource != null) {
-                sourceEntity.setUrl(imgSource.getUrl());
-                sourceEntity.setPosJson(imgSource.getPosJson());
-                sourceEntity.setMemberId(imgSource.getMemberId());
-            }
-            sourceEntity.setVideoUrl(url);
-            sourceEntity.setFaceSampleId(faceSample.getId());
-            sourceEntity.setMemberId(task.getMemberId());
-            sourceEntity.setScenicId(faceSample.getScenicId());
-            sourceEntity.setDeviceId(faceSample.getDeviceId());
-            sourceEntity.setType(1);
-            sourceMapper.add(sourceEntity);
-        } catch (FileNotFoundException e) {
-            throw new RuntimeException(e);
+        IStorageAdapter adapter = StorageFactory.use("assets");
+        String url = adapter.uploadFile(outFile, "video-source", outFile.getName());
+        SourceEntity imgSource = sourceMapper.findBySampleId(faceSample.getId());
+        SourceEntity sourceEntity = new SourceEntity();
+        sourceEntity.setId(SnowFlakeUtil.getLongId());
+        if (imgSource != null) {
+            sourceEntity.setUrl(imgSource.getUrl());
+            sourceEntity.setPosJson(imgSource.getPosJson());
+            sourceEntity.setMemberId(imgSource.getMemberId());
         }
+        sourceEntity.setVideoUrl(url);
+        sourceEntity.setFaceSampleId(faceSample.getId());
+        sourceEntity.setMemberId(task.getMemberId());
+        sourceEntity.setScenicId(faceSample.getScenicId());
+        sourceEntity.setDeviceId(faceSample.getDeviceId());
+        sourceEntity.setType(1);
+        sourceMapper.add(sourceEntity);
     }
 
     public boolean startFfmpegTask(FfmpegTask task) {
diff --git a/src/main/java/com/ycwl/basic/utils/OssUtil.java b/src/main/java/com/ycwl/basic/utils/OssUtil.java
deleted file mode 100644
index d6f9d20..0000000
--- a/src/main/java/com/ycwl/basic/utils/OssUtil.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package com.ycwl.basic.utils;
-
-import cn.hutool.core.date.DateUtil;
-import com.aliyun.oss.ClientException;
-import com.aliyun.oss.HttpMethod;
-import com.aliyun.oss.OSS;
-import com.aliyun.oss.OSSClientBuilder;
-import com.aliyun.oss.OSSException;
-import com.aliyun.oss.model.PutObjectRequest;
-import com.ycwl.basic.config.OssConfig;
-import com.ycwl.basic.enums.BizCodeEnum;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import java.io.InputStream;
-import java.net.URI;
-import java.util.Date;
-
-@Slf4j
-@Component
-public class OssUtil {
-
-    @Autowired
-    private OssConfig ossConfig;
-
-    /**
-     * 上传文件到oss
-     * @param inputStream 文件数据流
-     * @param filename 文件全路径名称
-     * @return
-     */
-    public String uploadFile(InputStream inputStream, String filename) {
-        return uploadFile(inputStream, ossConfig.getObjectName(), filename);
-    }
-
-    public String uploadAssetFile(InputStream inputStream, String filename) {
-        return uploadFile(inputStream, "assets/", filename);
-    }
-
-    public String uploadFile(InputStream inputStream, String path, String filename) {
-        String uploadFileName = path + filename;
-        OSS ossClient = new OSSClientBuilder().build(ossConfig.getEndPoint(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());
-        try {
-            PutObjectRequest putObjectRequest = new PutObjectRequest(ossConfig.getBucketName(), uploadFileName, inputStream);
-            ossClient.putObject(putObjectRequest);
-            String fileUrl = ossConfig.getUrl() + uploadFileName;
-            return fileUrl;
-        } catch (OSSException oe) {
-            log.error("Caught an OSSException, which means your request made it to OSS, "
-                    + "but was rejected with an error response for some reason."
-                    + " \n Error Message:" + oe.getErrorMessage()
-                    + " \n Error Code:" + oe.getErrorCode()
-                    + " \n Request ID:" + oe.getRequestId()
-                    + " \n Host ID:" + oe.getHostId()
-            );
-
-        } catch (ClientException ce) {
-            log.error("Caught an ClientException, which means the client encountered "
-                    + "a serious internal problem while trying to communicate with OSS, "
-                    + "such as not being able to access the network."
-                    + "Error Message:" + ce.getMessage());
-        } finally {
-            if (ossClient != null) {
-                ossClient.shutdown();
-            }
-        }
-
-        return BizCodeEnum.UPLOAD_FAILED.getMessage();
-    }
-
-    public String generateSignedUrlForDownload(String path, String filename) {
-        String downloadFile = path + filename;
-        return generateSignedUrl(downloadFile, HttpMethod.GET);
-    }
-
-    public String generateSignedUrlForUpload(String path, String filename) {
-        String uploadFileName = path + filename;
-        return generateSignedUrl(uploadFileName, HttpMethod.PUT);
-    }
-
-    public String generateSignedUrl(String objectName, HttpMethod method) {
-        OSS ossClient = new OSSClientBuilder().build(ossConfig.getEndPoint(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());
-        return ossClient.generatePresignedUrl(ossConfig.getBucketName(), objectName, DateUtil.offsetHour(new Date(), 1), method).toString();
-    }
-    public String generateUrlOfFile(String path, String filename) {
-        String objectName = path + filename;
-        return ossConfig.getUrl() + objectName;
-    }
-
-    public boolean deleteFile(String filename) {
-        // 填写文件完整路径。文件完整路径中不能包含Bucket名称。
-        String objectName = filename;
-
-        OSS ossClient = new OSSClientBuilder().build(ossConfig.getEndPoint(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());
-        try {
-            // 删除文件或目录。如果要删除目录,目录必须为空。
-            ossClient.deleteObject(ossConfig.getBucketName(), objectName);
-            return true;
-        } catch (OSSException oe) {
-            log.error("Caught an OSSException, which means your request made it to OSS, "
-                    + "but was rejected with an error response for some reason."
-                    + " \n Error Message:" + oe.getErrorMessage()
-                    + " \n Error Code:" + oe.getErrorCode()
-                    + " \n Request ID:" + oe.getRequestId()
-                    + " \n Host ID:" + oe.getHostId()
-            );
-        } catch (ClientException ce) {
-            log.error("Caught an ClientException, which means the client encountered "
-                    + "a serious internal problem while trying to communicate with OSS, "
-                    + "such as not being able to access the network."
-                    + "Error Message:" + ce.getMessage());
-        } finally {
-            if (ossClient != null) {
-                ossClient.shutdown();
-            }
-        }
-        return false;
-    }
-
-    public void deleteFileByUrl(String faceUrl) {
-        URI uri = URI.create(faceUrl);
-        String objectName = uri.getPath();
-        deleteFile(objectName);
-    }
-}
diff --git a/src/main/java/com/ycwl/basic/videoTask/VideoTaskFactory.java b/src/main/java/com/ycwl/basic/videoTask/VideoTaskFactory.java
new file mode 100644
index 0000000..f287f3a
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/videoTask/VideoTaskFactory.java
@@ -0,0 +1,4 @@
+package com.ycwl.basic.videoTask;
+
+public class VideoTaskFactory {
+}
diff --git a/src/main/java/com/ycwl/basic/videoTask/adapters/DefaultAdapter.java b/src/main/java/com/ycwl/basic/videoTask/adapters/DefaultAdapter.java
new file mode 100644
index 0000000..6fba4a3
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/videoTask/adapters/DefaultAdapter.java
@@ -0,0 +1,4 @@
+package com.ycwl.basic.videoTask.adapters;
+
+public class DefaultAdapter implements IAdapter {
+}
diff --git a/src/main/java/com/ycwl/basic/videoTask/adapters/IAdapter.java b/src/main/java/com/ycwl/basic/videoTask/adapters/IAdapter.java
new file mode 100644
index 0000000..db45ece
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/videoTask/adapters/IAdapter.java
@@ -0,0 +1,4 @@
+package com.ycwl.basic.videoTask.adapters;
+
+public interface IAdapter {
+}
diff --git a/src/main/java/com/ycwl/basic/videoTask/chains/VideoTaskGeneratorChain.java b/src/main/java/com/ycwl/basic/videoTask/chains/VideoTaskGeneratorChain.java
new file mode 100644
index 0000000..df18ffd
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/videoTask/chains/VideoTaskGeneratorChain.java
@@ -0,0 +1,22 @@
+package com.ycwl.basic.videoTask.chains;
+
+import com.ycwl.basic.videoTask.chains.interceptor.IVidTaskGenChainInterceptor;
+import com.ycwl.basic.videoTask.entity.VideoTaskGeneratorContext;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class VideoTaskGeneratorChain {
+    private final List<IVidTaskGenChainInterceptor> interceptors = new ArrayList<>();
+    private int index = 0;
+
+    public void addInterceptor(IVidTaskGenChainInterceptor interceptor) {
+        interceptors.add(interceptor);
+    }
+
+    public void process(VideoTaskGeneratorContext context) {
+        if (index < interceptors.size()) {
+            interceptors.get(index++).process(context, this);
+        }
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/videoTask/chains/interceptor/CheckFaceCountInterceptor.java b/src/main/java/com/ycwl/basic/videoTask/chains/interceptor/CheckFaceCountInterceptor.java
new file mode 100644
index 0000000..bbf33de
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/videoTask/chains/interceptor/CheckFaceCountInterceptor.java
@@ -0,0 +1,11 @@
+package com.ycwl.basic.videoTask.chains.interceptor;
+
+import com.ycwl.basic.videoTask.chains.VideoTaskGeneratorChain;
+import com.ycwl.basic.videoTask.entity.VideoTaskGeneratorContext;
+
+public class CheckFaceCountInterceptor implements IVidTaskGenChainInterceptor {
+    @Override
+    public void process(VideoTaskGeneratorContext ctx, VideoTaskGeneratorChain chain) {
+
+    }
+}
diff --git a/src/main/java/com/ycwl/basic/videoTask/chains/interceptor/IVidTaskGenChainInterceptor.java b/src/main/java/com/ycwl/basic/videoTask/chains/interceptor/IVidTaskGenChainInterceptor.java
new file mode 100644
index 0000000..461e10d
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/videoTask/chains/interceptor/IVidTaskGenChainInterceptor.java
@@ -0,0 +1,8 @@
+package com.ycwl.basic.videoTask.chains.interceptor;
+
+import com.ycwl.basic.videoTask.chains.VideoTaskGeneratorChain;
+import com.ycwl.basic.videoTask.entity.VideoTaskGeneratorContext;
+
+public interface IVidTaskGenChainInterceptor {
+    void process(VideoTaskGeneratorContext ctx, VideoTaskGeneratorChain chain);
+}
diff --git a/src/main/java/com/ycwl/basic/videoTask/daos/TemplateDao.java b/src/main/java/com/ycwl/basic/videoTask/daos/TemplateDao.java
new file mode 100644
index 0000000..c0774a8
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/videoTask/daos/TemplateDao.java
@@ -0,0 +1,4 @@
+package com.ycwl.basic.videoTask.daos;
+
+public class TemplateDao {
+}
diff --git a/src/main/java/com/ycwl/basic/videoTask/entity/VideoTaskGeneratorContext.java b/src/main/java/com/ycwl/basic/videoTask/entity/VideoTaskGeneratorContext.java
new file mode 100644
index 0000000..8726497
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/videoTask/entity/VideoTaskGeneratorContext.java
@@ -0,0 +1,4 @@
+package com.ycwl.basic.videoTask.entity;
+
+public class VideoTaskGeneratorContext {
+}
diff --git a/src/main/java/com/ycwl/basic/videoTask/enums/VideoTaskTriggerReason.java b/src/main/java/com/ycwl/basic/videoTask/enums/VideoTaskTriggerReason.java
new file mode 100644
index 0000000..77ad06c
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/videoTask/enums/VideoTaskTriggerReason.java
@@ -0,0 +1,22 @@
+package com.ycwl.basic.videoTask.enums;
+
+
+import lombok.Getter;
+import lombok.Setter;
+
+public enum VideoTaskTriggerReason {
+    DEVICE_UPLOAD("设备检测人脸"),
+    USER_MANUAL("用户手动触发"),
+    ADMIN_MANUAL("后台手动触发"),
+    SCHEDULE("定时触发"),
+    USER_FACE_UPLOAD("用户上传人脸触发");
+
+    @Getter
+    private final String desc;
+    @Getter
+    @Setter
+    private String extData;
+    VideoTaskTriggerReason(String desc) {
+        this.desc = desc;
+    }
+}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index 03c9181..c40828e 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -119,17 +119,32 @@ YfkdFNxtYLdVAwuylMoV3fKI
 face:
   score: 80
 
-#阿里云OSS
-aliYunOss:
-  endpoint: "https://oss-cn-shanghai.aliyuncs.com"
-  accessKeyId: "LTAI5tCa641QdNHH9Ybg9u7V"
-  accessKeySecret: "RRVIgekoqx96Fgm2Gs7eQshMShcEpk"
-  bucketName: "frametour-assets"
-  objectName: "user-faces/"
-  url: "https://frametour-assets.oss-cn-shanghai.aliyuncs.com/"
-  region: "cn-shanghai"
+# 存储
+storage:
+  configs:
+    - name: "faces"
+      type: "ALI_OSS"
+      config:
+        endpoint: "https://oss-cn-shanghai.aliyuncs.com"
+        accessKeyId: "LTAI5tCa641QdNHH9Ybg9u7V"
+        accessKeySecret: "RRVIgekoqx96Fgm2Gs7eQshMShcEpk"
+        bucketName: "frametour-assets"
+        prefix: "user-faces/"
+        url: "https://frametour-assets.oss-cn-shanghai.aliyuncs.com/"
+        region: "cn-shanghai"
+    - name: "assets"
+      type: "ALI_OSS"
+      config:
+        endpoint: "https://oss-cn-shanghai.aliyuncs.com"
+        accessKeyId: "LTAI5tCa641QdNHH9Ybg9u7V"
+        accessKeySecret: "RRVIgekoqx96Fgm2Gs7eQshMShcEpk"
+        bucketName: "frametour-assets"
+        prefix: "user-assets/"
+        url: "https://oss.zhentuai.com"
+        region: "cn-shanghai"
+
 #阿里云人脸检测
-aliYunFace:
+aliFace:
   accessKeyId: "LTAI5tMwrmxVcUEKoH5QzLHx"
   accessKeySecret: "ZCIP8aKx1jwX1wkeYIPQEDZ8fPtN1c"
   region: "cn-shanghai"
\ No newline at end of file
diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml
index 469925e..2fc85f0 100644
--- a/src/main/resources/application-prod.yml
+++ b/src/main/resources/application-prod.yml
@@ -117,17 +117,32 @@ YfkdFNxtYLdVAwuylMoV3fKI
 face:
   score: 80
 
-#阿里云OSS
-aliYunOss:
-  endpoint: "https://oss-cn-shanghai.aliyuncs.com"
-  accessKeyId: "LTAI5tCa641QdNHH9Ybg9u7V"
-  accessKeySecret: "RRVIgekoqx96Fgm2Gs7eQshMShcEpk"
-  bucketName: "frametour-assets"
-  objectName: "user-faces/"
-  url: "https://frametour-assets.oss-cn-shanghai.aliyuncs.com/"
-  region: "cn-shanghai"
+# 存储
+storage:
+  configs:
+    - name: "faces"
+      type: "ALI_OSS"
+      config:
+        endpoint: "https://oss-cn-shanghai-internal.aliyuncs.com"
+        accessKeyId: "LTAI5tCa641QdNHH9Ybg9u7V"
+        accessKeySecret: "RRVIgekoqx96Fgm2Gs7eQshMShcEpk"
+        bucketName: "frametour-assets"
+        prefix: "user-faces/"
+        url: "https://frametour-assets.oss-cn-shanghai.aliyuncs.com/"
+        region: "cn-shanghai"
+    - name: "assets"
+      type: "ALI_OSS"
+      config:
+        endpoint: "https://oss-cn-shanghai-internal.aliyuncs.com"
+        accessKeyId: "LTAI5tCa641QdNHH9Ybg9u7V"
+        accessKeySecret: "RRVIgekoqx96Fgm2Gs7eQshMShcEpk"
+        bucketName: "frametour-assets"
+        prefix: "user-assets/"
+        url: "https://oss.zhentuai.com"
+        region: "cn-shanghai"
+
 #阿里云人脸检测
-aliYunFace:
+aliFace:
   accessKeyId: "LTAI5tMwrmxVcUEKoH5QzLHx"
   accessKeySecret: "ZCIP8aKx1jwX1wkeYIPQEDZ8fPtN1c"
   region: "cn-shanghai"
\ No newline at end of file