logo

百度网盘定时同步备份文件夹

作者:千魔啸夜2022.05.09 12:08浏览量:967

简介:工作期间如何保持多台电脑之间文档定时备份呢?

简介:通过压缩文件夹和百度网盘api,定时备份文件夹到百度网盘

最近疫情在家,工作的电脑和台式机经常互相切换。一想到庞大的工作文件夹中的代码在两台电脑切换时候会有遗漏,该怎么解决呢?

1.首先想到的是使用百度网盘自带的文件夹备份工具,哈哈哈高贵的百度网盘超会会员在这个时候可以派上用场了!
1651812397718.jpg
可是一看这个文件夹数量只有5个可以选择,加上工作资料大多涉密,那应该怎么办呢?

2.突然想到直接压缩文档并且加密压缩文件夹来进行备份,百度网盘能同步文档,但是没办法读到加密后的压缩文件内容,做个定时任务直接把加密后的工作压缩文件夹同步到百度网盘岂不美哉?
1651812566630.jpg

3.查阅了百度网盘api,发现里面还需要不小的接口调用,首先是获取AccessToken

@Configuration
public class BaiduRouter {
    @Autowired
    private BaiduOAuthHandler baiduOAuthHandler;

    @Autowired
    private BaiduUploadHandler baiduUploadHandler;

    @Autowired
    private BaiduFileHandler baiduFileUpload;

    @Bean
    public RouterFunction<ServerResponse> baiduOAuthRouter() {

        return route(GET("/startAuthorizeCode"), baiduOAuthHandler::startAuthorizeCode) // 发起授权码 Code 请求
                .andRoute(GET("/exchangeAccessToken"), baiduOAuthHandler::exchangeAccessToken)  // 换取 Access Token 凭证
                .andRoute(GET("/refreshAccessToken"), baiduOAuthHandler::refreshAccessToken) // 刷新 Access Token
                .andRoute(GET("/getAccessToken"), baiduOAuthHandler::getAccessToken) //获取 Access Token
                .andRoute(GET("/baiduDiskNotify"), baiduOAuthHandler::baiduDiskNotify); //Access Token 回调地址

    }

    @Bean
    public RouterFunction<ServerResponse> baiduUploadRouter() {

        return route(POST("/precreateUpload"), baiduUploadHandler::precreateUpload) // 百度网盘预上传
                .andRoute(POST("/splitBaiduFileAndUpload"), baiduUploadHandler::splitBaiduFileAndUpload) // 百度网盘临时分片文件上传
                .andRoute(POST("/splitTempFileCreateFile"), baiduUploadHandler::splitTempFileCreateFile);
    }

    @Bean
    public RouterFunction<ServerResponse> baiduFileRouter() {

        return route(POST("/uploadFileToDisk"), baiduFileUpload::baiduFileUpload); // 发起授权码 Code 请求

    }
}
@Service
@Slf4j
public class BaiduOAuthServiceImpl implements BaiduOAuthService {

    @Value("${baidu.oauth.endpoint}")
    private String baiduEndpoint;

    @Value("${baidu.oauth.token.url}")
    private String baiduTokenUrl;

    @Value("${baidu.oauth.authorize.url}")
    private String baiduAuthorizeUrl;

    @Value("${baidu.oauth.client_id}")
    private String baiduClientId;

    @Value("${baidu.oauth.redirect_uri}")
    private String baiduRedirectUri;

    @Value("${baidu.oauth.client_secret}")
    private String baiduClientSecret;

    @Value("${baidu.oauth.device_id}")
    private String baiduDeviceId;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private FileUtils fileUtils;

    @Override
    @SysLog(MethodEnum.START_AUTH)
    public URI startAuthorizeCode() {

        return URI.create(new BuildReqUrl(baiduEndpoint + baiduAuthorizeUrl)
                .set(RESPONSE_TYPE, ResponseType.CODE)
                .set(CLIENT_ID, baiduClientId)
                .set(REDIRECT_URI, baiduRedirectUri)
                .set(SCOPE, Scope.BAIDU_DISK)
                .set(DEVICE_ID, baiduDeviceId)
                .toString());
    }

    @Override
    @SysLog(MethodEnum.EXCHANGE_ACCESS)
    public BaiduAccessReturn exchangeAccessToken(String authorizeCode) throws Exception {

        String uriToken = new BuildReqUrl(baiduEndpoint + baiduTokenUrl)
                .set(GRANT_TYPE, GrantType.AUTHORIZATION_CODE)
                .set(CODE, authorizeCode)
                .set(CLIENT_ID, baiduClientId)
                .set(CLIENT_SECRET, baiduClientSecret)
                .set(REDIRECT_URI, baiduRedirectUri)
                .toString();

        BaiduAccessReturn baiduAccess = HttpHelper.sendGet(uriToken, MapUtil.newHashMap() , MethodEnum.EXCHANGE_ACCESS ).obj(BaiduAccessReturn.class);

        //设置Access Token
        redisUtils.set(RedisKeyConfig.redisAccessTokenReturn(), baiduAccess.getAccess_token() , baiduAccess.getExpires_in());

        //重新刷新Refresh Token
        redisUtils.set(RedisKeyConfig.redisRefreshTokenReturn(), baiduAccess.getRefresh_token());

        return baiduAccess;
    }

    @Override
    @SysLog(MethodEnum.REFRESH_TOKEN)
    public BaiduAccessReturn refreshAccessToken(String refreshToken) throws Exception {

        if(StringUtils.isBlank(refreshToken)) {
            refreshToken = redisUtils.get(RedisKeyConfig.redisRefreshTokenReturn());
        }

        String uriToken = new BuildReqUrl(baiduEndpoint + baiduTokenUrl)
                .set(GRANT_TYPE, GrantType.REFRESH_TOKEN)
                .set(REFRESH_TOKEN, refreshToken)
                .set(CLIENT_ID, baiduClientId)
                .set(CLIENT_SECRET, baiduClientSecret)
                .toString();

        BaiduAccessReturn baiduAccess = HttpHelper.sendGet(uriToken, MapUtil.newHashMap() , MethodEnum.REFRESH_TOKEN ).obj(BaiduAccessReturn.class);

        //设置Access Token
        redisUtils.set(RedisKeyConfig.redisAccessTokenReturn(), baiduAccess.getAccess_token(), baiduAccess.getExpires_in());

        //重新刷新Refresh Token
        redisUtils.set(RedisKeyConfig.redisRefreshTokenReturn(), baiduAccess.getRefresh_token());

        return baiduAccess;
    }

    @Override
    @SysLog(MethodEnum.GET_ACCESS)
    public URI getAccessToken() {

        return URI.create(new BuildReqUrl(baiduEndpoint + baiduAuthorizeUrl)
                .set(RESPONSE_TYPE, ResponseType.TOKEN)
                .set(CLIENT_ID, baiduClientId)
                .set(REDIRECT_URI, baiduRedirectUri)
                .set(SCOPE, Scope.BAIDU_DISK)
                .toString());

    }
}
package com.puscnock.baidu_yun.baidu.handler;

import com.puscnock.baidu_yun.baidu.entity.BaiduAccessReturn;
import com.puscnock.baidu_yun.baidu.entity.Result;
import com.puscnock.baidu_yun.baidu.enums.BaiduOAuthEnum;
import com.puscnock.baidu_yun.baidu.service.BaiduOAuthService;
import io.netty.util.internal.StringUtil;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import static org.springframework.web.reactive.function.server.ServerResponse.ok;
import static org.springframework.web.reactive.function.server.ServerResponse.seeOther;

@Component
public class BaiduOAuthHandler {

    @Autowired
    private BaiduOAuthService baiduOAuthService;

    @SneakyThrows
    public @NotNull Mono<ServerResponse> startAuthorizeCode(ServerRequest serverRequest){
        return seeOther(baiduOAuthService.startAuthorizeCode()).build();
    }

    @SneakyThrows
    public @NotNull Mono<ServerResponse> exchangeAccessToken(ServerRequest serverRequest){
        return ok().body(Mono.just(Result.ok(baiduOAuthService.exchangeAccessToken(serverRequest.queryParam(BaiduOAuthEnum.AUTHORIZE_CODE).orElse(StringUtil.EMPTY_STRING)))),Result.class);
    }

    @SneakyThrows
    public @NotNull Mono<ServerResponse> refreshAccessToken(ServerRequest serverRequest){
        return ok().body(Mono.just(Result.ok(baiduOAuthService.refreshAccessToken(serverRequest.queryParam(BaiduOAuthEnum.REFRESH_TOKEN).orElse(StringUtil.EMPTY_STRING)))),Result.class);
    }

    @SneakyThrows
    public @NotNull Mono<ServerResponse> getAccessToken(ServerRequest serverRequest){
        return seeOther(baiduOAuthService.getAccessToken()).build();
    }

    @SneakyThrows
    public @NotNull Mono<ServerResponse> baiduDiskNotify(ServerRequest serverRequest){
        return ok().body(Mono.just(Result.ok(baiduOAuthService.baiduDiskNotify(serverRequest.bodyToMono(BaiduAccessReturn.class).toProcessor().peek()))),Result.class);
    }


}

4.然后就是编写百度网盘的上传接口了!上传分为预上传,分片上传,合并创建文件,这几个步骤!

@Component
public class BaiduUploadHandler {

    @Autowired
    private BaiduUploadService baiduUploadService;

    @SneakyThrows
    public @NotNull Mono<ServerResponse> precreateUpload(ServerRequest serverRequest) {
        return ok().body(Mono.just(Result.ok(baiduUploadService.preUploadFileToBaiduDisk(serverRequest.bodyToMono(MultipartFile.class).toProcessor().peek()))), Result.class);
    }

    @SneakyThrows
    public @NotNull Mono<ServerResponse> splitBaiduFileAndUpload(ServerRequest serverRequest) {
        return ok().body(Mono.just(Result.ok(baiduUploadService.splitBaiduFileAndUpload(null,serverRequest.bodyToMono(MultipartFile.class).toProcessor().peek()))), Result.class);
    }

    @SneakyThrows
    public Mono<ServerResponse> splitTempFileCreateFile(ServerRequest serverRequest) {
        return ok().body(Mono.just(Result.ok(baiduUploadService.splitTempFileCreateFile(null,serverRequest.bodyToMono(MultipartFile.class).toProcessor().peek()))), Result.class);
    }
}
@Service
@Slf4j
public class BaiduUploadServiceImpl implements BaiduUploadService {

    @Value("${baidu.upload.endpoint}")
    private String baiduUploadEndpoint;

    @Value("${baidu.upload.url}")
    private String baiduUploadUrl;

    @Value("${baidu.upload.split.endpoint}")
    private String baiduSplitEndpoint;

    @Value("${baidu.upload.split.url}")
    private String baiduSplitUrl;

    @Autowired
    private RedisUtils redisUtils;

    @SysLog(MethodEnum.PRE_UPLOAD)
    @Override
    public BaiduPreReturn preUploadFileToBaiduDisk(File uploadFile) throws Exception {

        String accessToken = redisUtils.get(RedisKeyConfig.redisAccessTokenReturn());

        String fileArrayMd5Str = FileUtils.separate(uploadFile, 4).getFileArrayMd5Str();

        String uriUpload = new BuildReqUrl(baiduUploadEndpoint + baiduUploadUrl)
                .set(ACCESS_TOKEN, accessToken)
                .set(METHOD, BaiduOAuthEnum.Method.PRECREATE)
                .toString();

        BaiduUpload baiduUpload = BaiduUpload.builder()
                .path(BaiduPublicEnum.APP_ROUTE + uploadFile.getName())
                .size(uploadFile.length())
                .isdir(0)
                .block_list("[\"" + fileArrayMd5Str + "\"]")
                .autoinit(1)
                .build();

        String linkString = BuildReqUrl.createLinkString(baiduUpload);
        log.info(linkString);

        String returnObj = FileUtils.open(uriUpload, BuildReqUrl.createLinkString(baiduUpload), "POST");
        return  JSONObject.parseObject(returnObj, BaiduPreReturn.class);
    }

    @Override
    public BaiduPreReturn preUploadFileToBaiduDisk(MultipartFile uploadFile) throws Exception {
        File tempUploadFile = new File("/" + DateUtil.current() + uploadFile.getOriginalFilename());
        uploadFile.transferTo(tempUploadFile);
        return preUploadFileToBaiduDisk(tempUploadFile);
    }

    @Override
    @SysLog(MethodEnum.FILE_UPLOAD)
    public Boolean splitBaiduFileAndUpload(BaiduPreReturn baiduPreReturn, MultipartFile uploadFile) throws Exception {
        File tempUploadFile = new File("/" + DateUtil.current() + uploadFile.getOriginalFilename());
        uploadFile.transferTo(tempUploadFile);
        return splitBaiduFileAndUpload(tempUploadFile, baiduPreReturn);
    }

    @ExceptionRetry
    @Override
    public Boolean splitBaiduFileAndUpload(File uploadFile, BaiduPreReturn baiduPreReturn) throws GlobalException {

        String accessToken = redisUtils.get(RedisKeyConfig.redisAccessTokenReturn());

        File[] splitFileArray = FileUtils.getFileParam(uploadFile ,uploadFile.getParentFile()).getFileParam();

        for (int i = 0; i < splitFileArray.length; i++) {
            String returnJson = sendFileAndUpload(uploadFile, baiduPreReturn, accessToken, splitFileArray[i] , i );
            log.info(uploadFile.getName() + "正在上传分片文件{}{}", returnJson, i);
        }

        return true;
    }

    @Nullable
    private String sendFileAndUpload(File uploadFile, BaiduPreReturn baiduPreReturn, String accessToken, File file , Integer count) {

        String uriUpload = new BuildReqUrl(baiduSplitEndpoint + baiduSplitUrl)
                .set(METHOD, BaiduOAuthEnum.Method.UPLOAD)
                .set(ACCESS_TOKEN, accessToken)
                .set(TYPE, BaiduOAuthEnum.Type.TMPFILE)
                .set(PATH, BaiduPublicEnum.APP_ROUTE + uploadFile.getName())
                .set(UPLOADID, baiduPreReturn.getUploadid())
                .set(PARTSEQ, count)
                .toString();

        return FileUtils.sendFile(uriUpload, file);

    }

    @SysLog(MethodEnum.FILE_CREATE)
    @Override
    public BaiduCreateReturn splitTempFileCreateFile(BaiduPreReturn baiduPreReturn ,File uploadFile) throws Exception {

        String accessToken = redisUtils.get(RedisKeyConfig.redisAccessTokenReturn());

        String uriUpload = new BuildReqUrl(baiduUploadEndpoint + baiduUploadUrl)
                .set(ACCESS_TOKEN, accessToken)
                .set(METHOD, BaiduOAuthEnum.Method.CREATE)
                .toString();

        File parentFile = uploadFile.getParentFile();
        String fileArrayMd5Str = FileUtils.getFileParam(uploadFile, parentFile).getFileArrayMd5Str();

        BaiduCreate baiduUpload = BaiduCreate.builder()
                .path(BaiduPublicEnum.APP_ROUTE + uploadFile.getName())
                .size(uploadFile.length())
                .isdir(0)
                .uploadid(baiduPreReturn.getUploadid())
                .block_list("[\"" + fileArrayMd5Str + "\"]")
                .build();

        String returnObj = FileUtils.open(uriUpload, BuildReqUrl.createLinkString(baiduUpload), "POST");
        return JSONObject.parseObject(returnObj, BaiduCreateReturn.class);

    }

    @Override
    public BaiduCreateReturn splitTempFileCreateFile(BaiduPreReturn baiduPreReturn ,MultipartFile uploadFile) throws Exception {
        File tempUploadFile = new File("/" + DateUtil.current() + uploadFile.getOriginalFilename());
        uploadFile.transferTo(tempUploadFile);
        return splitTempFileCreateFile( baiduPreReturn ,tempUploadFile);
    }
}
public class BaiduOAuthEnum {

    /**
     * 百度OAuth授权码
     */
    public static String AUTHORIZE_CODE = "authorizeCode";

    /**
     * 百度OAuth刷新Token
     */
    public static String REFRESH_TOKEN = "refreshToken";


    /**
     * 百度OAuth 请求头
     */
    public static class Param {

        public static String RESPONSE_TYPE = "response_type";

        public static String CLIENT_ID = "client_id";

        public static String CLIENT_SECRET = "client_secret";

        public static String GRANT_TYPE = "grant_type";

        public static String REDIRECT_URI = "redirect_uri";

        public static String CODE = "code";

        public static String SCOPE = "scope";

        public static String DEVICE_ID = "device_id";

        public static String REFRESH_TOKEN = "refresh_token";

        public static String METHOD = "method";

        public static String ACCESS_TOKEN = "access_token";

        public static String PATH = "path";

        public static String TYPE = "type";

        public static String UPLOADID = "uploadid";

        public static String PARTSEQ = "partseq";

    }

    public static class GrantType {

        public static String AUTHORIZATION_CODE = "authorization_code";

        public static String REFRESH_TOKEN = "refresh_token";

    }

    public static class ResponseType {

        public static String CODE = "code";

        public static String TOKEN = "token";

    }

    public static class Scope {

        public static String BAIDU_DISK = "basic,netdisk";

    }

    public static class Method {

        public static String PRECREATE = "precreate";

        public static String CREATE = "create";

        public static String UPLOAD = "upload";

    }

    public static class Type{

        public static String TMPFILE = "tmpfile";
    }
}

5.上传完成就是定时任务啦!定时任务直接挂在那儿定时备份需要上传的文件夹就行啦!

@Component
@Slf4j
public class BaiduAccessTask {

    @Autowired
    private BaiduOAuthService baiduOAuthService;

    @Autowired
    private BaiduFileService baiduFileService;

    @Autowired
    private BaiduUploadService baiduUploadService;

    @Value("${baidu.upload.zip.path}")
    private String tempZipFile;

    /**
     * 自动刷新 Access Token 任务 —— 1 小时运行 1 次
     * @throws GlobalException
     */
    @Scheduled(cron = "0 0 0/1 * * ?")
    @ExceptionRetry
    public void refreshToken() throws GlobalException {
        try{
            log.info(">>>> Refresh Token 定时任务运行!");
            baiduOAuthService.refreshAccessToken(StringUtil.EMPTY_STRING);
            log.info(">>>> Refresh Token 定时任务结束!");
        }catch (Exception e){
             throw new GlobalException(">>>> Refresh Token 定时任务出错!");
        }
    }

    /**
     * 每日同步网盘文件 —— 1 小时运行 1 次
     */
    @Scheduled(cron = "0 0/30 * * * ?")
    @ExceptionRetry
    @SysLog(MethodEnum.DISK_NOTIFY)
    public void dailyUploadDisk() throws Exception {

        log.info(">>>> Daily Upload 同步定时任务运行!");

        List<FilePojo> fileList = baiduFileService.baiduFileListFind();

        for (FilePojo filePojo : fileList) {

            String fileName = filePojo.getFileName();

            String filePath = filePojo.getFilePath();

            String zipTempFilePath = FileUtils.getZipTempFilePath(tempZipFile);

            String tempPwd = filePojo.getPwd();

            File uploadFile = new File(FileUtils.getFilePathAndFileName(filePath, fileName));

            File filePathFor = new File(FileUtils.getZipTempFilePath(tempZipFile));

            if(filePathFor.exists()) {
                for (File file : filePathFor.listFiles()) {
                    file.delete();
                }
            }

            File zipFile = new File(FileUtils.getAbsoluteZipTempFilePath(tempZipFile ,fileName));

            ZipFileUtils.encodeZip(uploadFile,
                    zipTempFilePath,
                    fileName,
                    tempPwd,
                    false,
                    zipFile);

            BaiduPreReturn baiduPreReturn = baiduUploadService.preUploadFileToBaiduDisk(zipFile);

            baiduUploadService.splitBaiduFileAndUpload(zipFile, baiduPreReturn);

            baiduUploadService.splitTempFileCreateFile(baiduPreReturn, zipFile);

            log.info(">>>> "+ uploadFile.getName() +"文件上传成功!");

        }

        log.info(">>>> Daily Upload 同步定时任务结束!");

    }

}

7.刚进来时候有可能AccessToken过期了,开机启动时候需要做个初始化刷新一下AccessToken!

@Component
@Slf4j
public class BaiduInit {

    @Autowired
    private BaiduOAuthService baiduOAuthService;

    @PostConstruct
    public void init() throws Exception {
        log.info(">>>> Refresh Token 初始化任务运行!");
        baiduOAuthService.refreshAccessToken(StringUtil.EMPTY_STRING);
        log.info(">>>> Refresh Token 初始化任务运行!");
    }
}

8.当!当!当!然后就可以享受不限流量定时备份文件到百度网盘啦!
1651813122715.jpg

谢谢大家观看!

发表评论