dengsixing hace 3 años
padre
commit
eacd46d281

+ 35 - 0
.gitignore

@@ -0,0 +1,35 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Java template
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### Example user template template
+### Example user template
+
+# IntelliJ project files
+.idea
+*.iml
+out
+gen
+/**/target/

+ 31 - 0
src/main/java/com/fdkankan/download/SceneDownloadApplication.java

@@ -0,0 +1,31 @@
+package com.fdkankan.download;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+/**
+ * <p>
+ * TODO
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/2/22
+ **/
+@SpringBootApplication
+@EnableScheduling
+@EnableFeignClients("com.fdkankan.*.api.feign")
+@ComponentScan(basePackages = {"com.fdkankan.*"})
+@RefreshScope
+@EnableAsync
+public class SceneDownloadApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(SceneDownloadApplication.class, args);
+    }
+}
+
+

+ 36 - 0
src/main/java/com/fdkankan/download/bean/CurrentDownloadNumUtil.java

@@ -0,0 +1,36 @@
+package com.fdkankan.download.bean;
+
+import cn.hutool.core.collection.ConcurrentHashSet;
+
+/**
+ * <p>
+ * TODO
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/3/29
+ **/
+public class CurrentDownloadNumUtil {
+
+    /**
+     * 正在下载的场景码
+     */
+    private static ConcurrentHashSet<String> downLoadingNumSet = new ConcurrentHashSet<>();
+
+    public static void addSceneNum(String num){
+        downLoadingNumSet.add(num);
+    }
+
+    public static boolean containSceneNum(String num){
+        return downLoadingNumSet.contains(num);
+    }
+
+    public static void removeSceneNum(String num){
+        downLoadingNumSet.remove(num);
+    }
+
+    public static int cntDownloadingLocal(){
+        return downLoadingNumSet.size();
+    }
+
+}

+ 28 - 0
src/main/java/com/fdkankan/download/bean/ImageType.java

@@ -0,0 +1,28 @@
+package com.fdkankan.download.bean;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * <p>
+ * TODO
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/2/24
+ **/
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ImageType {
+
+    private String name;
+
+    private String size;
+
+    private String[] ranges;
+
+}

+ 27 - 0
src/main/java/com/fdkankan/download/bean/ImageTypeDetail.java

@@ -0,0 +1,27 @@
+package com.fdkankan.download.bean;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * <p>
+ * TODO
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/2/24
+ **/
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ImageTypeDetail {
+
+    private String i;
+    private String j;
+    private String x;
+    private String y;
+
+}

+ 52 - 0
src/main/java/com/fdkankan/download/config/TaskPoolConfig.java

@@ -0,0 +1,52 @@
+package com.fdkankan.download.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * <p>
+ * TODO
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/2/22
+ **/
+@Configuration
+public class TaskPoolConfig {
+
+    @Bean("sceneDownLoadExecutror")
+    public ThreadPoolTaskExecutor sceneDownLoadExecutror(){
+        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
+        taskExecutor.setCorePoolSize(3);
+        taskExecutor.setMaxPoolSize(3);
+        taskExecutor.setQueueCapacity(3);
+        taskExecutor.setKeepAliveSeconds(60);
+        taskExecutor.setThreadNamePrefix("sceneDownLoadThread--");
+        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
+        taskExecutor.setAwaitTerminationSeconds(60);
+        return taskExecutor;
+    }
+
+//    @Bean
+//    public ExecutorCompletionService completionService(ThreadPoolTaskExecutor sceneDownLoadExecutror){
+//        return new ExecutorCompletionService<Integer>(sceneDownLoadExecutror);
+//    }
+
+    @Bean
+    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
+        return new RestTemplate(factory);
+    }
+
+    @Bean
+    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
+        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+        factory.setReadTimeout(5000);//ms
+        factory.setConnectTimeout(15000);//ms
+        return factory;
+    }
+
+}

+ 66 - 0
src/main/java/com/fdkankan/download/config/TestController.java

@@ -0,0 +1,66 @@
+package com.fdkankan.download.config;
+
+import com.alibaba.fastjson.JSON;
+import com.fdkankan.fyun.oss.UploadToOssUtil;
+import com.fdkankan.redis.util.RedisUtil;
+import com.fdkankan.common.bean.DownLoadTaskBean;
+import com.fdkankan.download.service.impl.SceneDownloadHandlerServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * <p>
+ * TODO
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/2/24
+ **/
+@RestController
+@RequestMapping("/test")
+@Slf4j
+public class TestController {
+
+    @Autowired
+    UploadToOssUtil uploadToOssUtil;
+
+    @Autowired
+    SceneDownloadHandlerServiceImpl downloadHandlerService;
+    @Autowired
+    RedisUtil redisUtil;
+
+    @GetMapping("/test")
+    public String test() throws Exception{
+//        Map<String, String> param = new HashMap<>();
+//        param.put("num", "t-YhBCzQr");
+//        String getInfoStr = restTemplate.postForObject(serverUrl + "api/scene/getInfo?num=t-YhBCzQr", param, String.class);
+//
+//        ResultData resultData = JSONUtil.toBean(getInfoStr, ResultData.class);
+//        if(resultData == null || ServerCode.SUCCESS.code() != resultData.getCode()){
+//            log.error("getInfo请求失败,url:" + serverUrl + "api/scene/getInfo?num=t-YhBCzQr");
+//        }
+//
+//        JSONObject dataJson = JSONUtil.parseObj(resultData.getData());
+//        dataJson.set("sceneScheme", 3);
+//        dataJson.set("needKey", 0);
+//        dataJson.set("sceneKey","");
+
+//        List<String> strings = uploadToOssUtil.listKeysFromAli("data/datat-ieXdyGl6Md");
+//        for (String string : strings) {
+//            System.out.println(string);
+//        }
+
+        redisUtil.lLeftPush("downloads:task:v4", JSON
+            .toJSONString(DownLoadTaskBean.builder().num("eur-3wdeOQaa0").type("local").build()));
+
+//        List<String> strings = uploadToOssUtil.listKeysFromAws("data/datawJ6cAq3tc");
+
+        return "123";
+    }
+
+}

+ 25 - 0
src/main/java/com/fdkankan/download/schedule/ScheduleJob.java

@@ -0,0 +1,25 @@
+package com.fdkankan.download.schedule;
+
+import com.fdkankan.download.service.ISceneDownLoadService;
+import com.fdkankan.redis.util.RedisUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class ScheduleJob {
+
+    @Autowired
+    RedisUtil redisUtil;
+    @Autowired
+    ISceneDownLoadService sceneDownLoadService;
+
+    @Scheduled(cron = "0/5 * * * * ? ")
+    public void job4SceneDownload() throws Exception {
+        log.info("场景下载定时任务执行。。。。");
+        sceneDownLoadService.process();
+    }
+
+}

+ 15 - 0
src/main/java/com/fdkankan/download/service/ISceneDownLoadService.java

@@ -0,0 +1,15 @@
+package com.fdkankan.download.service;
+
+/**
+ * <p>
+ * 场景下载接口
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/2/22
+ **/
+public interface ISceneDownLoadService {
+
+    void process() throws Exception;
+
+}

+ 64 - 0
src/main/java/com/fdkankan/download/service/impl/CheckProgressRunnerImpl.java

@@ -0,0 +1,64 @@
+package com.fdkankan.download.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.alibaba.fastjson.JSON;
+import com.fdkankan.redis.constant.RedisKey;
+import com.fdkankan.redis.constant.RedisLockKey;
+import com.fdkankan.redis.util.RedisLockUtil;
+import com.fdkankan.redis.util.RedisUtil;
+import com.fdkankan.common.bean.DownLoadTaskBean;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+/**
+ * <p>
+ * 应用启动校验是否有下载任务正在进行,如有过就重新入队头,从新下载
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/2/22
+ **/
+@Component
+public class CheckProgressRunnerImpl implements CommandLineRunner {
+
+    @Autowired
+    RedisUtil redisUtil;
+    @Autowired
+    RedisTemplate redisTemplate;
+    @Autowired
+    RedisLockUtil redisLockUtil;
+
+    @Override
+    public void run(String... args) throws Exception {
+
+        //这里考虑到如果是集群部署,可能多个节点启动时,只需要一个节点进来就行了,所以加一个分布式锁
+        String lockKey = String.format(RedisLockKey.LOCK_SCENE_DOWNLOAD_ING);
+        boolean lock = redisLockUtil.lock(lockKey, RedisKey.EXPIRE_TIME_1_MINUTE);
+        if(!lock){
+            return;
+        }
+        try {
+            //查询还没下载完毕的场景
+            List<String> downLoadList = redisUtil.lGet(RedisKey.SCENE_DOWNLOAD_ING, 0, -1);
+            if(CollUtil.isEmpty(downLoadList)){
+                return;
+            }
+            //删除还没执行完毕的场景缓存
+            redisUtil.del(RedisKey.SCENE_DOWNLOAD_ING);
+
+            List<String> taskList = downLoadList.stream().map(num -> {
+                return JSON.toJSONString(DownLoadTaskBean.builder().num(num).type("local").build());
+            }).collect(Collectors.toList());
+
+            //从新入队
+            redisUtil.lLeftPushAll(RedisKey.SCENE_DOWNLOADS_TASK_V4, taskList);
+
+        }finally {
+            redisLockUtil.unlockLua(lockKey);
+        }
+    }
+}

+ 85 - 0
src/main/java/com/fdkankan/download/service/impl/SceneDownLoadServiceImpl.java

@@ -0,0 +1,85 @@
+package com.fdkankan.download.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.fdkankan.common.bean.DownLoadTaskBean;
+import com.fdkankan.download.bean.CurrentDownloadNumUtil;
+import com.fdkankan.download.service.ISceneDownLoadService;
+import com.fdkankan.redis.constant.RedisKey;
+import com.fdkankan.redis.util.RedisUtil;
+import java.util.Objects;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * TODO
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/2/22
+ **/
+@RefreshScope
+@Service
+public class SceneDownLoadServiceImpl implements ISceneDownLoadService {
+
+    @Value("${scene.download.threadMax:3}")
+    private Integer downloadThreadMax;
+
+    @Autowired
+    RedisUtil redisUtil;
+
+//    @Autowired
+//    ExecutorCompletionService completionService;
+
+    @Autowired
+    SceneDownloadHandlerServiceImpl handlerService;
+
+
+    @Override
+    public void process() throws Exception {
+
+        //统计本节点正在下载任务数量
+        int downloadIngCnt = CurrentDownloadNumUtil.cntDownloadingLocal();
+        //如果正在下载的场景大于最大线程数,不往下执行
+        if(downloadIngCnt >= downloadThreadMax){
+            return;
+        }
+
+        for(int i = 0; i < downloadThreadMax - downloadIngCnt; i++){
+            DownLoadTaskBean downLoadTaskBean = this.getTaskSceneNum();
+            //获取任务队列中队头场景码,如果是空,标识没有场景要下载,则退出程序
+            if(Objects.isNull(downLoadTaskBean)){
+                continue;
+            }
+            handlerService.download(downLoadTaskBean);
+        }
+
+    }
+
+    private DownLoadTaskBean getTaskSceneNum() throws Exception{
+        //redis待下载任务出队
+        String downloadTask = redisUtil.lLeftPop(RedisKey.SCENE_DOWNLOADS_TASK_V4);
+        if(StrUtil.isEmpty(downloadTask)){
+            return null;
+        }
+        DownLoadTaskBean downLoadTaskBean = JSONUtil.toBean(downloadTask, DownLoadTaskBean.class);
+        if(downLoadTaskBean == null || StrUtil.isEmpty(downLoadTaskBean.getNum()) || !"local".equals(downLoadTaskBean.getType())){
+            throw new Exception("下载任务数据不正确,downloadTask:" +  downloadTask);
+        }
+        //如果场景正在下载中,就直接丢弃
+        String num = downLoadTaskBean.getNum();
+        if(CurrentDownloadNumUtil.containSceneNum(num)){
+            return null;
+        }
+        //本地缓存入队
+        CurrentDownloadNumUtil.addSceneNum(num);
+        //正在下载任务入队
+        redisUtil.lLeftPush(RedisKey.SCENE_DOWNLOAD_ING, num);
+        return downLoadTaskBean;
+    }
+
+
+}

+ 530 - 0
src/main/java/com/fdkankan/download/service/impl/SceneDownloadHandlerServiceImpl.java

@@ -0,0 +1,530 @@
+package com.fdkankan.download.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ConcurrentHashSet;
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
+import com.fdkankan.common.bean.DownLoadProgressBean;
+import com.fdkankan.common.bean.DownLoadTaskBean;
+import com.fdkankan.common.constant.SceneDownloadProgressStatus;
+import com.fdkankan.common.constant.SceneFrom;
+import com.fdkankan.common.constant.SceneResolution;
+import com.fdkankan.common.constant.ServerCode;
+import com.fdkankan.common.constant.UploadFilePath;
+import com.fdkankan.common.response.ResultData;
+import com.fdkankan.common.util.FileUtils;
+import com.fdkankan.download.bean.CurrentDownloadNumUtil;
+import com.fdkankan.download.bean.ImageType;
+import com.fdkankan.download.bean.ImageTypeDetail;
+import com.fdkankan.fyun.constant.StorageType;
+import com.fdkankan.fyun.oss.UploadToOssUtil;
+import com.fdkankan.redis.constant.RedisKey;
+import com.fdkankan.redis.util.RedisUtil;
+import com.fdkankan.scene.api.dto.SceneInfoDTO;
+import com.fdkankan.scene.api.feign.SceneUserSceneClient;
+import com.google.common.collect.Lists;
+import java.io.File;
+import java.io.FileInputStream;
+import java.math.BigDecimal;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+import lombok.var;
+import org.apache.tools.zip.ZipOutputStream;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * <p>
+ * TODO
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/2/22
+ **/
+@RefreshScope
+@Slf4j
+@Service
+public class SceneDownloadHandlerServiceImpl {
+
+    private static final String[] prefixArr = new String[]{
+        UploadFilePath.DATA_VIEW_PATH,
+        UploadFilePath.VOICE_VIEW_PATH,
+        UploadFilePath.VIDEOS_VIEW_PATH,
+        UploadFilePath.IMG_VIEW_PATH,
+        UploadFilePath.USER_VIEW_PATH,
+    };
+
+    private static final List<ImageType> imageTypes = Lists.newArrayList();
+    static{
+        imageTypes.add(ImageType.builder().name("2k_face").size("2048").ranges(new String[]{"0", "511", "1023", "1535"}).build());
+        imageTypes.add(ImageType.builder().name("1k_face").size("1024").ranges(new String[]{"0", "511"}).build());
+        imageTypes.add(ImageType.builder().name("512_face").size("512").ranges(new String[]{"0"}).build());
+    }
+
+    @Autowired
+    private SceneUserSceneClient sceneUserSceneClient;
+
+    @Value("${path.v4school}")
+    private String v4localPath;
+
+    @Value("${path.zip-local}")
+    private String zipLocalFormat;
+
+    @Value("${path.zip-oss}")
+    private String zipOssFormat;
+
+    @Value("${path.zip-root}")
+    private String wwwroot;
+
+    @Value("${zip.nThreads}")
+    private int zipNthreads;
+
+    @Value("${oss.bucket:4dkankan}")
+    private String bucket;
+
+    @Value("${upload.type:oss}")
+    private String uploadType;
+    @Value("${download.config.resource-url}")
+    private String resourceUrl;
+    @Value("${download.config.public-url}")
+    private String publicUrl;
+    @Value("${download.config.exe-name}")
+    private String exeName;
+    @Value("${download.config.exe-content}")
+    private String exeContent;
+    @Autowired
+    RestTemplate restTemplate;
+
+    @Autowired
+    RedisUtil redisUtil;
+    @Autowired
+    UploadToOssUtil uploadToOssUtil;
+
+    @Async("sceneDownLoadExecutror")
+    public void download(DownLoadTaskBean downLoadTaskBean){
+        //场景码
+        String num = null;
+
+        try {
+            num = downLoadTaskBean.getNum();
+
+            log.info("场景下载开始 - num[{}] - threadName[{}]", num, Thread.currentThread().getName());
+
+            long startTime = Calendar.getInstance().getTimeInMillis();
+
+            //执行场景下载逻辑
+            this.downloadHandler(downLoadTaskBean);
+
+            //耗时
+            long consumeTime = Calendar.getInstance().getTimeInMillis() - startTime;
+
+            log.info("场景下载结束 - num[{}] - threadName[{}] - consumeTime[{}]", num, Thread.currentThread().getName(), consumeTime);
+
+        }catch (Exception e){
+            log.error(ExceptionUtil.stacktraceToString(e));
+        }finally {
+            if(StrUtil.isNotEmpty(num)){
+                //本地正在下载任务出队
+                CurrentDownloadNumUtil.removeSceneNum(num);
+                //删除正在下载任务
+                redisUtil.lRemove(RedisKey.SCENE_DOWNLOAD_ING, 1, num);
+            }
+        }
+    }
+
+    public void downloadHandler(DownLoadTaskBean downLoadTaskBean) throws Exception{
+
+        String num = downLoadTaskBean.getNum();
+        //zip包路径
+        String zipPath = null;
+
+        try {
+            Set<String> cacheKeys = new ConcurrentHashSet<>();
+
+            Map<String, List<String>> allFiles = this.getAllFiles(num, v4localPath);
+            List<String> ossFilePaths = allFiles.get("ossFilePaths");
+            List<String> v3localFilePaths = allFiles.get("v3localFilePaths");
+
+            //key总个数
+            int total = ossFilePaths.size() + v3localFilePaths.size();
+            AtomicInteger count = new AtomicInteger(0);
+            //定义压缩包
+            zipPath = String.format(this.zipLocalFormat, num);
+            File zipFile = new File(zipPath);
+            if(!zipFile.getParentFile().exists()){
+                zipFile.getParentFile().mkdirs();
+            }
+            ZipOutputStream out = new ZipOutputStream(zipFile);
+
+//            JSONObject getInfoJson = this.zipGetInfoJson(out, this.wwwroot, num);
+            String sceneJsonData = uploadToOssUtil.getObjectContent(bucket, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "scene.json");
+            JSONObject sceneJson = JSONUtil.parseObj(sceneJsonData);
+            String resolution = "4k";
+            String sceneForm = sceneJson.getStr("sceneFrom");
+            if(StrUtil.isNotEmpty(sceneForm) && SceneFrom.PRO.code().equals(sceneForm)){
+                resolution = "2k";
+            }
+            //国际版存在已经切好图的情况,下载时不需要再切图,只需要把文件直接下载下来打包就可以了
+            String sceneResolution = sceneJson.getStr("sceneResolution");
+            if(SceneResolution.TILES.code().equals(sceneResolution)){
+                resolution = "notNeadCut";
+            }
+
+            int imagesVersion = -1;
+            // TODO: 2022/3/29  V4版本目前没有imagesVersion字段,暂时用version字段替代
+//            if(getInfoJson.getInt("imagesVersion") != null){
+//                imagesVersion = getInfoJson.getInt("imagesVersion");
+//            }
+            Integer version = sceneJson.getInt("version");
+            if(Objects.nonNull(version)){
+                imagesVersion = version;
+            }
+
+
+            long start = Calendar.getInstance().getTimeInMillis();
+
+            //固定文件写入
+            this.zipLocalFiles(out, v3localFilePaths, v4localPath, num, count, total);
+            long end1 = Calendar.getInstance().getTimeInMillis();
+            log.info("打包固定文件耗时, num:{}, time:{}", num, end1 - start);
+
+            //oss文件写入
+            this.zipOssFiles(out, ossFilePaths, num, count, total, resolution, imagesVersion, cacheKeys);
+            long end2 = Calendar.getInstance().getTimeInMillis();
+            log.info("打包oss文件耗时, num:{}, time:{}", num, end2 - end1);
+
+            //重新写入scene.json(去掉密码访问设置)
+            this.zipSceneJson(out, this.wwwroot, num, sceneJson);
+
+            //写入启动命令
+            this.zipBat(out, num);
+
+            out.close();
+
+            //上传压缩包
+            String uploadPath = String.format(this.zipOssFormat, num);
+            uploadToOssUtil.uploadBySh(zipPath, uploadPath);
+
+            //更新进度100
+            String url = this.publicUrl + uploadPath + "?t=" + Calendar.getInstance().getTimeInMillis();
+            this.updateProgress(null, num, SceneDownloadProgressStatus.DOWNLOAD_SUCCESS.code(), url);
+
+            // TODO: 2022/5/24 v3 停止后要开启-----------------------start
+            //更新用户场景已下载次数
+//            platformUserClient.updateDownloadNum(userId, 1);
+//
+//            //更新下载log状态为成功
+//            sceneUserSceneClient.updateSceneDownloadLog(num, DownloadStatus.SUCCESS.code(), url, null);
+            // TODO: 2022/5/24 v3 停止后要开启-----------------------end
+
+        }catch (Exception e){
+            //更新进度为下载失败
+            this.updateProgress( null, num, SceneDownloadProgressStatus.DOWNLOAD_FAILED.code(), null);
+            //更新下载log状态为成功
+            // TODO: 2022/5/24 v3 停止后要开启-----------------------start
+//            sceneUserSceneClient.updateSceneDownloadLog(num, DownloadStatus.FAILD.code(), null, ExceptionUtil.stacktraceToString(e));
+            // TODO: 2022/5/24 v3 停止后要开启-----------------------send
+            throw e;
+        }finally {
+            if(StrUtil.isNotBlank(zipPath)){
+                //删除本地zip包
+                FileUtils.deleteFile(zipPath);
+            }
+        }
+    }
+
+    private void zipOssFiles(ZipOutputStream out, List<String> ossFilePaths, String num, AtomicInteger count,
+        int total, String resolution, int imagesVersion, Set<String> cacheKeys) throws Exception{
+        String imageNumPath = String.format(UploadFilePath.IMG_VIEW_PATH, num);
+        ExecutorService executorService = Executors.newFixedThreadPool(this.zipNthreads);
+        List<Future> futureList = new ArrayList<>();
+        for (String filePath : ossFilePaths) {
+            Callable<Boolean> call = new Callable() {
+                @Override
+                public Boolean call() throws Exception {
+                    zipOssFilesHandler(out, num, count, total, resolution,
+                        imagesVersion, cacheKeys,filePath, imageNumPath);
+                    return true;
+                }
+            };
+            futureList.add(executorService.submit(call));
+        }
+        //这里一定要加阻塞,不然会导致oss文件还没打包好,主程序已经结束返回了
+        for (Future future : futureList) {
+            future.get();
+        }
+    }
+
+    private void zipOssFilesHandler(ZipOutputStream out, String num,
+        AtomicInteger count, int total, String resolution,
+        int imagesVersion, Set<String> cacheKeys,
+        String filePath, String imageNumPath) throws Exception{
+
+        //更新进度
+        this.updateProgress(new BigDecimal(count.incrementAndGet()).divide(new BigDecimal(total), 6, BigDecimal.ROUND_HALF_UP),
+            num, SceneDownloadProgressStatus.DOWNLOADING.code(), null);
+
+        //某个目录不需要打包
+        if(filePath.contains(imageNumPath + "panorama/panorama_edit/"))
+            return;
+
+        //切图
+        if(!"notNeadCut".equals(resolution)){
+            if((filePath.contains(imageNumPath + "panorama/") && filePath.contains("tiles/" + resolution))
+                || filePath.contains(imageNumPath + "tiles/" + resolution + "/")) {
+                this.processImage(filePath, out, resolution, imagesVersion, cacheKeys);
+                return;
+            }
+        }
+
+        //其他文件打包
+        this.ProcessFiles(num, filePath, out, this.wwwroot, cacheKeys);
+    }
+
+    private void zipLocalFiles(ZipOutputStream out, List<String> v3localFilePaths, String v3localPath, String num, AtomicInteger count, int total) throws Exception{
+        for (String v3localFilePath : v3localFilePaths) {
+            try (FileInputStream in = new FileInputStream(new File(v3localFilePath));){
+                this.zipInputStream(out, v3localFilePath.replace(v3localPath, ""), in);
+            }catch (Exception e){
+                throw e;
+            }
+            //更新进度
+            this.updateProgress(
+                new BigDecimal(count.incrementAndGet()).divide(new BigDecimal(total), 6, BigDecimal.ROUND_HALF_UP),
+                num, SceneDownloadProgressStatus.DOWNLOAD_COMPRESSING.code(), null);
+        }
+        //写入code.txt
+        this.zipBytes(out, "code.txt", num.getBytes());
+    }
+
+    private void zipBat(ZipOutputStream out, String num) throws Exception{
+        String batContent = String.format(this.exeContent, num);
+        this.zipBytes(out, exeName, batContent.getBytes());
+
+        //更新进度为90%
+        this.updateProgress(new BigDecimal("0.9").divide(new BigDecimal("0.8"), 6, BigDecimal.ROUND_HALF_UP), num,
+            SceneDownloadProgressStatus.DOWNLOAD_COMPRESSING.code(), null);
+    }
+
+    private Map<String, List<String>> getAllFiles(String num, String v3localPath) throws Exception{
+        //列出oss所有文件路径
+        List<String> ossFilePaths = new ArrayList<>();
+        for (String prefix : prefixArr) {
+            prefix = String.format(prefix, num);
+            List<String> keys = uploadToOssUtil.listKeys(prefix);
+            if(CollUtil.isEmpty(keys)){
+                continue;
+            }
+            if(StorageType.AWS.code().equals(this.uploadType)){
+                keys = keys.stream().filter(key->{
+                    if(key.contains("x-oss-process")){
+                        return false;
+                    }
+                    return true;
+                }).collect(Collectors.toList());
+            }
+            ossFilePaths.addAll(keys);
+        }
+
+        //列出v3local所有文件路径
+        File file = new File(v3localPath);
+        List<String> v3localFilePaths = FileUtils.list(file);
+
+        HashMap<String, List<String>> map = new HashMap<>();
+        map.put("ossFilePaths", ossFilePaths);
+        map.put("v3localFilePaths", v3localFilePaths);
+
+        return map;
+    }
+
+    private JSONObject zipGetInfoJson(ZipOutputStream out, String root, String num) throws Exception{
+
+        ResultData<SceneInfoDTO> sceneViewInfo = sceneUserSceneClient.getSceneViewInfo(num);
+        if(!sceneViewInfo.getSuccess()){
+            throw new Exception(ServerCode.FEIGN_REQUEST_FAILD.message());
+        }
+        SceneInfoDTO data = sceneViewInfo.getData();
+        JSONObject getInfoJson = null;
+        if(Objects.isNull(data)){
+            getInfoJson = new JSONObject();
+        }else {
+            getInfoJson = JSONUtil.parseObj(data);
+        }
+
+        getInfoJson.set("sceneScheme", 3);
+        getInfoJson.set("needKey", 0);
+        getInfoJson.set("sceneKey","");
+        //写入getInfo.json
+        String getInfoJsonPath = root + String.format(UploadFilePath.DATA_VIEW_PATH, num) + "getInfo.json";
+        this.zipBytes(out, getInfoJsonPath, getInfoJson.toString().getBytes());
+        return getInfoJson;
+    }
+
+    private void zipSceneJson(ZipOutputStream out, String root, String num, JSONObject sceneJson) throws Exception{
+
+        //访问密码置0
+        JSONObject controls = sceneJson.getJSONObject("controls");
+        controls.set("showLock", 0);
+
+        String sceneJsonPath = root + String.format(UploadFilePath.DATA_VIEW_PATH, num) + "scene.json";
+        this.zipBytes(out, sceneJsonPath, sceneJson.toString().getBytes());
+    }
+
+    private void processImage(String key, ZipOutputStream out, String resolution, int imagesVersion, Set<String> imgKeys) throws Exception{
+
+        if(key.contains("x-oss-process") || key.endsWith("/")){
+            return;
+        }
+
+        String fileName = key.substring(key.lastIndexOf("/")+1, key.indexOf("."));
+        String ext = key.substring(key.lastIndexOf("."));
+        String[] arr = fileName.split("_skybox");
+        String dir = arr[0];
+        String num = arr[1];
+        if(StrUtil.isEmpty(fileName)
+            || StrUtil.isEmpty(ext)
+            || (".jpg".equals(ext) && ".png".equals(ext))
+            ||  StrUtil.isEmpty(dir)
+            || StrUtil.isEmpty(num)){
+            throw new Exception("本地下载图片资源不符合规则,key:" + key);
+        }
+        for (ImageType imageType : imageTypes) {
+
+            List<ImageTypeDetail> items = Lists.newArrayList();
+            String[] ranges = imageType.getRanges();
+            for(int i = 0; i < ranges.length; i++){
+                String x = ranges[i];
+                for(int j = 0; j < ranges.length; j++){
+                    String y = ranges[j];
+                    items.add(
+                        ImageTypeDetail.builder()
+                            .i(String.valueOf(i))
+                            .j(String.valueOf(j))
+                            .x(x)
+                            .y(y)
+                            .build()
+                    );
+                }
+            }
+            for (ImageTypeDetail item : items) {
+                String par = "?x-oss-process=image/resize,m_lfit,w_" + imageType.getSize() + "/crop,w_512,h_512,x_" + item.getX() + ",y_" + item.getY();
+                if(StorageType.AWS.code().equals(uploadType)){
+                    par += "&imagesVersion="+ imagesVersion;
+                }
+
+                var url = this.
+                    resourceUrl + key;
+                StorageType storageType = StorageType.get(uploadType);
+                switch (storageType){
+                    case OSS:
+                        url += par;
+                        break;
+                    case AWS:
+                        url += URLEncoder.encode(par.replace("/", "@"), "UTF-8");
+                        break;
+                }
+                var fky = key.split("/" + resolution + "/")[0] + "/" + dir + "/" + imageType.getName() +  num + "_" + item.getI()  + "_" + item.getJ() + ext;
+                if(imgKeys.contains(fky)){
+                    continue;
+                }
+                imgKeys.add(fky);
+                this.zipBytes(out, wwwroot + fky, FileUtils.getBytesFromUrl(url));
+            }
+
+        }
+
+    }
+
+    public void ProcessFiles(String num, String key, ZipOutputStream out, String prefix, Set<String> cacheKeys) throws Exception{
+        if(cacheKeys.contains(key)){
+            return;
+        }
+        if(key.equals(String.format(UploadFilePath.DATA_VIEW_PATH, num) + "scene.json")){
+            return;
+        }
+        cacheKeys.add(key);
+        String url = this.resourceUrl + key + "?t=" + Calendar.getInstance().getTimeInMillis();
+        if(key.contains("hot.json") || key.contains("link-scene.json")){
+            String content = FileUtils.getStringFromUrl(url);
+
+            content.replace(publicUrl, "")
+//                .replace(publicUrl+"v3/", "")
+                .replace("https://spc.html","spc.html")
+                .replace("https://smobile.html", "smobile.html");
+
+            zipBytes(out, prefix + key, content.getBytes());
+        }else{
+            zipBytes(out, prefix + key, FileUtils.getBytesFromUrl(url));
+        }
+    }
+
+
+    public void updateProgress(BigDecimal precent, String num, Integer status, String url){
+
+        SceneDownloadProgressStatus progressStatus = SceneDownloadProgressStatus.get(status);
+        switch (progressStatus){
+            case DOWNLOAD_SUCCESS:
+                precent = new BigDecimal("100");
+                break;
+            case DOWNLOAD_FAILED:
+                precent = new BigDecimal("0");
+                break;
+            default:
+                precent = precent.multiply(new BigDecimal("0.8")).multiply(new BigDecimal("100"));
+        }
+
+        DownLoadProgressBean progress = null;
+        String key = String.format(RedisKey.PREFIX_DOWNLOAD_PROGRESS_V4, num);
+        String progressStr = redisUtil.get(key);
+        if(StrUtil.isEmpty(progressStr)){
+            progress =  DownLoadProgressBean.builder().percent(precent.intValue()).status(status).url(url).build();
+        }else{
+            progress = JSONUtil.toBean(progressStr, DownLoadProgressBean.class);
+            //如果下载失败,进度不变
+            if(status == SceneDownloadProgressStatus.DOWNLOAD_FAILED.code() && progress.getPercent() != null){
+                precent = new BigDecimal(progress.getPercent());
+            }
+            progress.setPercent(precent.intValue());
+            progress.setStatus(status);
+            progress.setUrl(url);
+        }
+        redisUtil.set(key, JSONUtil.toJsonStr(progress));
+
+    }
+
+    public void zipInputStream(ZipOutputStream out, String key, FileInputStream in) throws Exception {
+        out.putNextEntry(new org.apache.tools.zip.ZipEntry(key));
+        byte[] bytes = new byte[1024];
+        int b = 0;
+        while ((b = in.read(bytes)) != -1) {
+            out.write(bytes, 0, b);
+        }
+    }
+
+    public synchronized void zipBytes(ZipOutputStream out, String key, byte[] bytes) throws Exception {
+        out.putNextEntry(new org.apache.tools.zip.ZipEntry(key));
+        out.write(bytes);
+    }
+
+
+}

+ 37 - 0
src/main/resources/bootstrap-dev.yml

@@ -0,0 +1,37 @@
+spring:
+  jackson:
+    serialization:
+      #关闭jackson转换 实体属性空值校验
+      FAIL_ON_EMPTY_BEANS: false
+  application:
+    name: 4dkankan-center-scene-download
+  cloud:
+    nacos:
+      config:
+        server-addr: 192.168.0.47:8848
+        file-extension: yaml
+        namespace: 4dkankan-dev
+        extension-configs:
+          - data-id: 4dkankan-center-scene-download.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+        shared-configs:
+
+          - data-id: common-redis-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+
+          - data-id: other-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+
+          - data-id: common-upload-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+      discovery:
+        server-addr: 192.168.0.47:8848
+        namespace: 4dkankan-dev
+
+
+
+

+ 36 - 0
src/main/resources/bootstrap-pro-eur.yml

@@ -0,0 +1,36 @@
+spring:
+  jackson:
+    serialization:
+      #关闭jackson转换 实体属性空值校验
+      FAIL_ON_EMPTY_BEANS: false
+  application:
+    name: 4dkankan-center-scene-download
+  cloud:
+    nacos:
+      config:
+        server-addr: 172.31.42.151:8848
+        file-extension: yaml
+        namespace: 4dkankan-pro-eur
+        extension-configs:
+          - data-id: 4dkankan-center-scene-download.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+        shared-configs:
+
+          - data-id: common-redis-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+
+          - data-id: other-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+
+          - data-id: common-upload-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+      discovery:
+        server-addr: 172.31.42.151:8848
+        namespace: 4dkankan-pro-eur
+
+
+

+ 36 - 0
src/main/resources/bootstrap-pro.yml

@@ -0,0 +1,36 @@
+spring:
+  jackson:
+    serialization:
+      #关闭jackson转换 实体属性空值校验
+      FAIL_ON_EMPTY_BEANS: false
+  application:
+    name: 4dkankan-center-scene-download
+  cloud:
+    nacos:
+      config:
+        server-addr: 172.18.157.42:8848
+        file-extension: yaml
+        namespace: 4dkankan-pro
+        extension-configs:
+          - data-id: 4dkankan-center-scene-download.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+        shared-configs:
+
+          - data-id: common-redis-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+
+          - data-id: other-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+
+          - data-id: common-upload-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+      discovery:
+        server-addr: 172.18.157.42:8848
+        namespace: 4dkankan-pro
+
+
+

+ 35 - 0
src/main/resources/bootstrap-test-eur.yml

@@ -0,0 +1,35 @@
+spring:
+  jackson:
+    serialization:
+      #关闭jackson转换 实体属性空值校验
+      FAIL_ON_EMPTY_BEANS: false
+  application:
+    name: 4dkankan-center-scene-download
+  cloud:
+    nacos:
+      config:
+        server-addr: 120.24.144.164:8848
+        file-extension: yaml
+        namespace: 4dkankan-test-eur
+        extension-configs:
+          - data-id: 4dkankan-center-scene-download.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+        shared-configs:
+
+          - data-id: common-redis-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+
+          - data-id: other-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+
+          - data-id: common-upload-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+      discovery:
+        server-addr: 120.24.144.164:8848
+        namespace: 4dkankan-test-eur
+
+

+ 35 - 0
src/main/resources/bootstrap-test.yml

@@ -0,0 +1,35 @@
+spring:
+  jackson:
+    serialization:
+      #关闭jackson转换 实体属性空值校验
+      FAIL_ON_EMPTY_BEANS: false
+  application:
+    name: 4dkankan-center-scene-download
+  cloud:
+    nacos:
+      config:
+        server-addr: 120.24.144.164:8848
+        file-extension: yaml
+        namespace: 4dkankan-test
+        extension-configs:
+          - data-id: 4dkankan-center-scene-download.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+        shared-configs:
+
+          - data-id: common-redis-config-52.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+
+          - data-id: other-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+
+          - data-id: common-upload-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+      discovery:
+        server-addr: 120.24.144.164:8848
+        namespace: 4dkankan-test
+
+

+ 3 - 0
src/main/resources/bootstrap.yml

@@ -0,0 +1,3 @@
+spring:
+  profiles:
+    active: dev

+ 270 - 0
src/main/resources/logback-spring.xml

@@ -0,0 +1,270 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
+<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
+<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
+<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
+<configuration scan="true" scanPeriod="10 seconds">
+	<springProperty scope="context" name="LOG_PATH" source="logging.path"/>
+	<define name="hostName" class = "com.fdkankan.common.config.LogPathHostNameProperty"/>
+	<contextName>logback</contextName>
+	<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
+	<property name="log.path" value="${LOG_PATH}/download" />
+
+	<!-- 彩色日志 -->
+	<!-- 彩色日志依赖的渲染类 -->
+	<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
+	<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
+	<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
+	<!-- 彩色日志格式 -->
+	<property name="CONSOLE_LOG_PATTERN"
+		value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
+
+	<!--输出到控制台 -->
+	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+		<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息 -->
+		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+			<level>info</level>
+		</filter>
+		<encoder>
+			<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
+			<!-- 设置字符集 -->
+			<charset>UTF-8</charset>
+		</encoder>
+	</appender>
+	<!--输出到文件 -->
+
+	<!-- 时间滚动输出 level为 DEBUG 日志 -->
+	<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<!-- 正在记录的日志文件的路径及文件名 -->
+		<file>${log.path}/log_debug.log</file>
+		<!--日志文件输出格式 -->
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}[%L] - %msg%n</pattern>
+			<charset>UTF-8</charset> <!-- 设置字符集 -->
+		</encoder>
+		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<!-- 日志归档 -->
+			<fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log
+			</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>100MB</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!--日志文件保留天数 -->
+			<maxHistory>15</maxHistory>
+		</rollingPolicy>
+		<!-- 此日志文件只记录debug级别的 -->
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<level>debug</level>
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+
+	<!-- 时间滚动输出 level为 INFO 日志 -->
+	<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<!-- 正在记录的日志文件的路径及文件名 -->
+		<file>${log.path}/log_info.log</file>
+		<!--日志文件输出格式 -->
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}[%L] - %msg%n</pattern>
+			<charset>UTF-8</charset>
+		</encoder>
+		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<!-- 每天日志归档路径以及格式 -->
+			<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log
+			</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>100MB</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!--日志文件保留天数 -->
+			<maxHistory>15</maxHistory>
+		</rollingPolicy>
+		<!-- 此日志文件只记录info级别的 -->
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<level>info</level>
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+
+	<!-- 时间滚动输出 level为 WARN 日志 -->
+	<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<!-- 正在记录的日志文件的路径及文件名 -->
+		<file>${log.path}/log_warn.log</file>
+		<!--日志文件输出格式 -->
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}[%L] - %msg%n</pattern>
+			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
+		</encoder>
+		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log
+			</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>100MB</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!--日志文件保留天数 -->
+			<maxHistory>15</maxHistory>
+		</rollingPolicy>
+		<!-- 此日志文件只记录warn级别的 -->
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<level>warn</level>
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+
+
+	<!-- 时间滚动输出 level为 ERROR 日志 -->
+	<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<!-- 正在记录的日志文件的路径及文件名 -->
+		<file>${log.path}/log_error.log</file>
+		<!--日志文件输出格式 -->
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}[%L] - %msg%n</pattern>
+			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
+		</encoder>
+		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log
+			</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>100MB</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!--日志文件保留天数 -->
+			<maxHistory>15</maxHistory>
+		</rollingPolicy>
+		<!-- 此日志文件只记录ERROR级别的 -->
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<level>ERROR</level>
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+	
+	<appender name="PROGRAM_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<!-- 正在记录的日志文件的路径及文件名 -->
+		<file>${log.path}/program/log_program.log</file>
+		<!--日志文件输出格式 -->
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}[%L] - %msg%n</pattern>
+			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
+		</encoder>
+		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/program/log-program-%d{yyyy-MM-dd}.%i.log
+			</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>100MB</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!--日志文件保留天数 -->
+			<maxHistory>15</maxHistory>
+		</rollingPolicy>
+		<!-- 此日志文件只记录ERROR级别的 -->
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+	<logger name="programLog" level="INFO" additivity="true">
+        <appender-ref ref="PROGRAM_FILE"/>
+    </logger>
+
+	<appender name="VISIT_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<!-- 正在记录的日志文件的路径及文件名 -->
+		<file>${log.path}/visit/log_visit.log</file>
+		<!--日志文件输出格式 -->
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}[%L] - %msg%n</pattern>
+			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
+		</encoder>
+		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/visit/log-visit-%d{yyyy-MM-dd}.%i.log
+			</fileNamePattern>
+
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>100MB</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!--日志文件保留天数 -->
+			<maxHistory>15</maxHistory>
+		</rollingPolicy>
+		<!-- 此日志文件只记录ERROR级别的 -->
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+	<logger name="visitLog" level="INFO" additivity="true">
+        <appender-ref ref="VISIT_FILE"/>
+    </logger>
+
+	<!-- <logger>用来设置某一个包或者具体的某一个类的日志打印级别、 以及指定<appender>。<logger>仅有一个name属性, 一个可选的level和一个可选的addtivity属性。 name:用来指定受此logger约束的某一个包或者具体的某一个类。 level:用来设置打印级别,大小写无关:TRACE,
+		DEBUG, INFO, WARN, ERROR, ALL 和 OFF, 还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。 如果未设置此属性,那么当前logger将会继承上级的级别。 addtivity:是否向上级logger传递打印信息。默认是true。 -->
+	<!--<logger name="org.springframework.web" level="info"/> -->
+	<!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/> -->
+	<!-- 使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作: 第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息 第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别: -->
+	<!-- root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性 level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF, 不能设置为INHERITED或者同义词NULL。默认是DEBUG 可以包含零个或多个元素,标识这个appender将会添加到这个logger。 -->
+
+			<root level="info">
+				<appender-ref ref="CONSOLE" />
+				<appender-ref ref="DEBUG_FILE" />
+				<appender-ref ref="INFO_FILE" />
+				<appender-ref ref="WARN_FILE" />
+				<appender-ref ref="ERROR_FILE" />
+			</root>
+
+<!--	<springProfile name="dev">-->
+<!--		<root level="info">-->
+<!--			<appender-ref ref="CONSOLE" />-->
+<!--			<appender-ref ref="DEBUG_FILE" />-->
+<!--			<appender-ref ref="INFO_FILE" />-->
+<!--			<appender-ref ref="WARN_FILE" />-->
+<!--			<appender-ref ref="ERROR_FILE" />-->
+<!--			<appender-ref ref="ALL_FILE" />-->
+<!--		</root>-->
+<!--		&lt;!&ndash; <logger name="com.xusanduo.demo" level="debug"/>开发环境, 指定某包日志为debug级 &ndash;&gt;-->
+<!--	</springProfile>-->
+
+<!--	<springProfile name="test">-->
+<!--		<root level="info">-->
+<!--			<appender-ref ref="CONSOLE" />-->
+<!--			<appender-ref ref="DEBUG_FILE" />-->
+<!--			<appender-ref ref="INFO_FILE" />-->
+<!--			<appender-ref ref="WARN_FILE" />-->
+<!--			<appender-ref ref="ERROR_FILE" />-->
+<!--			<appender-ref ref="ALL_FILE" />-->
+<!--		</root>-->
+<!--		&lt;!&ndash; <logger name="com.xusanduo.demo" level="info"/> 测试环境, 指定某包日志为info级 &ndash;&gt;-->
+<!--	</springProfile>-->
+
+<!--	<springProfile name="pro">-->
+<!--		<root level="error">-->
+<!--			&lt;!&ndash; 生产环境最好不配置console写文件 &ndash;&gt;-->
+<!--			<appender-ref ref="CONSOLE" />-->
+<!--			<appender-ref ref="DEBUG_FILE" />-->
+<!--			<appender-ref ref="INFO_FILE" />-->
+<!--			<appender-ref ref="WARN_FILE" />-->
+<!--			<appender-ref ref="ERROR_FILE" />-->
+<!--			<appender-ref ref="ALL_FILE" />-->
+<!--		</root>-->
+<!--		&lt;!&ndash; <logger name="com.xusanduo.demo" level="warn"/> 生产环境, 指定某包日志为warn级 &ndash;&gt;-->
+<!--		&lt;!&ndash; <logger name="com.xusanduo.demo.MyApplication" level="info"/> 特定某个类打印info日志, 比如application启动成功后的提示语 &ndash;&gt;-->
+<!--	</springProfile>-->
+
+	<!--生产环境:输出到文件 -->
+	<!--<springProfile name="pro"> -->
+	<!--<root level="info"> -->
+	<!--<appender-ref ref="CONSOLE" /> -->
+	<!--<appender-ref ref="DEBUG_FILE" /> -->
+	<!--<appender-ref ref="INFO_FILE" /> -->
+	<!--<appender-ref ref="ERROR_FILE" /> -->
+	<!--<appender-ref ref="WARN_FILE" /> -->
+	<!--</root> -->
+	<!--</springProfile> -->
+
+</configuration>
+
+