前端和后台进行大量数据同步的一个小记

    最近在做一个前端收银的项目,其中一块很恶心的问题就是商品库数据同步的问题,来记录一下整个过程。

    由于商品同步的数据量较大,所以采用了文件同步的方式。

    整个逻辑流程如下:

     ①下载服务端数据(zip压缩包)→②解压并解析里面的文件列表(files)→③跟本地数据库的数据进行对比修改→④服务端数据对比修改完成后,打包本地差异数据

     →⑤将本地的差异数据包上传到服务端→⑥数据同步完成。

    准备工作:

    这边Android端采用的数据库框架是greenDao这个框架(确实很不错,用起来很舒服,像我这种很讨厌SQL编程的用起来居然也很顺手~~~~)

    整理一个文件相关操作的帮助类:

public class FileCacheHelper {
   /**
    * 将数据存入指定文件
    * @param file 指定的文件
    * @param data 要保存的数据
    * @throws IOException
    */
   public static void saveToCacheFile(File file, String data) throws IOException{
      FileOutputStream outputStream = new FileOutputStream(file);
      outputStream.write(data.getBytes());
      outputStream.close();
   }
   
   /**
    * 读取文件中的缓存数据
    * @param file
    * @return
    * @throws IOException
    */
   public static String readFromCacheFile(File file) throws IOException{
      String str = "";
      StringBuffer data = new StringBuffer();
      
      if(file.exists()){
         FileInputStream inputStream = new FileInputStream(file);
         BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
         
         while((str=reader.readLine()) != null){
            data.append(str);
         }
         
         inputStream.close();
      }
      
      return data.toString();
   }

   /**
    * 返回每一行的数据
    * @param file
    * @return
    * @throws IOException
    */
   public static List<String> readFromFile(File file) throws IOException {
      List<String> stringList = new ArrayList<>();
      if(file.exists()){
         FileInputStream inputStream = new FileInputStream(file);
         BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
         String str = "";
         while ((str=reader.readLine()) != null){
            stringList.add(str);
         }
         inputStream.close();
      }

      return stringList;
   }
   
   public static File createOrGetCacheFile(Context context,String fileName) throws IOException {
      String path = getCacahFilePath(context);
      path += File.separator+ fileName;
      Log.d("path", path);
      return new File(path);
   }

   //获取外部(内部)数据存储路径
   public static String getCacahFilePath(Context context){
      String path = "";
      if(context.getExternalCacheDir() != null){
         path = context.getExternalCacheDir().getAbsolutePath(); //外部路径
      }else{
         path = context.getFilesDir().getAbsolutePath();  //内部路径
      }

      return path;
   }

   //解压文件(获取文件列表)
   public static List<String> unzipFile(Context context, File file) throws IOException {
      List<String> fileNameList = new ArrayList<>();
      ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(file));
      ZipEntry zipEntry;
      String szName = "";
      while ((zipEntry = zipInputStream.getNextEntry()) != null){
         szName = zipEntry.getName();
         if(!zipEntry.isDirectory()){
            String destName = "";
            if(szName.contains("/")){
               String names[] = szName.split("/");
               destName = names[names.length - 1];
            }else{
               destName = szName;
            }

            File fileDest = FileCacheHelper.createOrGetCacheFile(context,destName);
            File fileDir = new File(fileDest.getParent());
            if(!fileDir.exists()){
               fileDir.mkdirs();
            }

            FileOutputStream fileOutputStream = new FileOutputStream(fileDest);
            byte[] buff = new byte[2048];
            int length;
            while ((length = zipInputStream.read(buff)) != -1){
               fileOutputStream.write(buff, 0 , length);
               fileOutputStream.flush();
            }
            fileOutputStream.close();
            fileNameList.add(destName);
         }
      }
      zipInputStream.close();

      return fileNameList;
   }

   /**
    * Compress file and folder
    * @param srcFileString   file or folder to be Compress
    * @param zipFileString   the path name of result ZIP
    * @throws Exception
    */
   public static void zipFolder(String srcFileString, String zipFileString)throws Exception {
      //create ZIP
      ZipOutputStream outZip = new ZipOutputStream(new FileOutputStream(zipFileString));
      //create the file
      File file = new File(srcFileString);
      //compress
      ZipFiles(file.getParent()+File.separator, file.getName(), outZip);
      //finish and close
      outZip.finish();
      outZip.close();
   }

   /**
    * compress files
    * @param folderString
    * @param fileString
    * @param zipOutputSteam
    * @throws Exception
    */
   private static void ZipFiles(String folderString, String fileString, ZipOutputStream zipOutputSteam)throws Exception{
      if(zipOutputSteam == null)
         return;
      File file = new File(folderString+fileString);
      if (file.isFile()) {
         ZipEntry zipEntry =  new ZipEntry(fileString);
         FileInputStream inputStream = new FileInputStream(file);
         zipOutputSteam.putNextEntry(zipEntry);
         int len;
         byte[] buffer = new byte[4096];
         while((len=inputStream.read(buffer)) != -1)
         {
            zipOutputSteam.write(buffer, 0, len);
         }
         zipOutputSteam.closeEntry();
      }
      else {
         //folder
         String fileList[] = file.list();
         //no child file and compress
         if (fileList.length <= 0) {
            ZipEntry zipEntry =  new ZipEntry(fileString+File.separator);
            zipOutputSteam.putNextEntry(zipEntry);
            zipOutputSteam.closeEntry();
         }
         //child files and recursion
         for (int i = 0; i < fileList.length; i++) {
            ZipFiles(folderString, fileString+java.io.File.separator+fileList[i], zipOutputSteam);
         }//end of for
      }
   }

   //保存的下载的文件到本地
   public static File saveFile(Context context,ResponseBody body){
      InputStream inputStream;
      byte[] buff = new byte[2048];
      int length;
      FileOutputStream fileOutputStream;
      File file = null;
      try {
         inputStream = body.byteStream();
         file = FileCacheHelper.createOrGetCacheFile(context,"syncData.zip");
         fileOutputStream = new FileOutputStream(file);
         while ((length = inputStream.read(buff)) != -1){
            fileOutputStream.write(buff, 0 , length);
            fileOutputStream.flush();
         }
         fileOutputStream.close();
      }catch (Exception e){
         e.printStackTrace();
      }
      return file;
   }
因为整个过程涉及到很多io操作和其中还穿插着接口调用,这个时候rxJava的好处就体现出来,(轻松穿梭于各个线程之间,哈哈~~),下面是主要同步的过程:

Flowable<MposResponseMsg> syncResult = MposApiManager.getInstance().getApiService().syncDown(param.getMapParam()); 
    syncResult.subscribeOn(Schedulers.newThread())                     //发出数据同步的请求
            .filter(new Predicate<MposResponseMsg>() {
                @Override
                public boolean test(MposResponseMsg mposResponseMsg) throws Exception {
                    return mposResponseMsg.syncDownResponse.success;   //收到请求成功的标识
                }
            })
            .flatMap(new Function<MposResponseMsg, Publisher<MposResponseMsg>>() {
                @Override
                public Publisher<MposResponseMsg> apply(MposResponseMsg mposResponseMsg) throws Exception {
                    final SyncFlowParam syncFlowParam = new SyncFlowParam();
                    syncFlowParam.setBatchNo(mposResponseMsg.syncDownResponse.batchNo);
                    return Flowable.interval(2, TimeUnit.SECONDS)
                            .flatMap(new Function<Long, Publisher<MposResponseMsg>>() {
                                @Override
                                public Publisher<MposResponseMsg> apply(Long aLong) throws Exception {
                                    return MposApiManager.getInstance().
                                           getApiService().syncFlow(syncFlowParam.getMapParam());
                                }     //轮询数据文件打包接口,等待数据打包,2秒一次,上限50次   
                            })                                        
                            .take(50)
                            .takeUntil(new Predicate<MposResponseMsg>() {
                                @Override
                                public boolean test(MposResponseMsg mposResponseMsg) throws Exception {
                                    return mposResponseMsg.sycnflowResponse.success 
                                }
                            });  //等到返回打包完成的标识,不再轮询,准备开始下载数据包
                }
            })
            .flatMap(new Function<MposResponseMsg, Publisher<ResponseBody>>() {
                @Override
                public Publisher<ResponseBody> apply(MposResponseMsg mposResponseMsg) throws Exception {
                    String fileUrl = mposResponseMsg.sycnflowResponse.synchronizeFlowListJson.get(0).getPath();
                    return ApiManager.getInstance().getDownloadService().downloadSyncFile(fileUrl);
                }                                                    ---//开始下载数据包
            })
            .observeOn(Schedulers.io())
            .map(new Function<ResponseBody, List<String>>() {
                @Override
                public List<String> apply(ResponseBody body) throws Exception {
                    return FileCacheHelper.unzipFile(AboutActivity.this, FileCacheHelper.saveFile(AboutActivity.this, body));
                }                                                   --//解压数据包,拿到文件列表
            })
            .flatMap(new Function<List<String>, Publisher<String>>() {
                @Override
                public Publisher<String> apply(List<String> strings) throws Exception {
                    fileNames.addAll(strings);
                    return Flowable.fromIterable(strings);        
                }
            })
            .map(new Function<String, File>() {
                @Override
                public File apply(String s) throws Exception {
                    return FileCacheHelper.createOrGetCacheFile(AboutActivity.this, s);    --//拿到单个数据文件
                }                                                  
            })
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new ApiSubscriberCallBack<File>() {
                @Override
                public void onSuccess(File file) { 
                   syncProduct(file);                                             --//开始同步数据文件
                }

                @Override
                public void onComplete() {
                    LoadingDialogHelper.dismiss();
                }
            });
}
下面是文件同步,同步完成后,打包本地差异数据,然后上传的服务器:

//同步商品数据
private void syncProduct(File file){
    LoadingDialogHelper.show(this);
    Flowable.just(file)
            .subscribeOn(Schedulers.io())
            ...(中间一系列的变换操作,数据库读写等)
            .subscribe(new ApiSubscriberCallBack<Product>() {
                @Override
                public void onSuccess(Product product) {
                    Utils.updateProduct(product);
                }

                @Override
                public void onComplete() {
                    Utils.deleteProdutDisabled();
                    uploadFiles();      //打包本地差异数据,准备上传
                }

                @Override
                public void onFailure(Throwable t) {
                    Log.d("error", t.getMessage());
                }
            });
}
打包差异数据并上传:

private void uploadFiles(){
    LoadingDialogHelper.show(this);
    Flowable<MposResponseMsg> uploadFile = MposApiManager.getInstance().getApiService().syncUpload(new SyncUploadParam().getMapParam());
    uploadFile.subscribeOn(Schedulers.newThread())           --//请求上传文件的接口
            .filter(new Predicate<MposResponseMsg>() {
                @Override
                public boolean test(MposResponseMsg mposResponseMsg) throws Exception {
                    return mposResponseMsg.syncUploadResponse.success;   --//请求成功,准备打包数据
                }
            })
            .observeOn(Schedulers.io())
            .flatMap(new Function<MposResponseMsg, Publisher<MposResponseMsg>>() {
                @Override
                public Publisher<MposResponseMsg> apply(MposResponseMsg mposResponseMsg) throws Exception {
                    String bNo = mposResponseMsg.syncUploadResponse.batchNo;
                    RequestBody batchNo = RequestBody.create(MediaType.parse("text/plain"), bNo);
                    RequestBody storeNo = RequestBody.create(MediaType.parse("text/plain"),MPosApplication.getInstance().getStore_no());
                    File zipFile = FileCacheHelper.initSyncFiles(AboutActivity.this);
                    RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), zipFile);
                    MultipartBody.Part part = MultipartBody.Part.createFormData(bNo, bNo + ".zip", requestFile);
                    return ApiManager.getInstance().getDownloadService().uploadSyncFile(part, batchNo, storeNo);
                }         ---------------//打包数据,并进行上传
            })
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new ApiSubscriberCallBack<MposResponseMsg>() {
                @Override
                public void onSuccess(MposResponseMsg mposResponseMsg) {
                    LoadingDialogHelper.dismiss();
                    showMessage1("数据同步完成");
                }
            });
}
整个核心过程,大概就是这些,刚用rxjava不久,有些过程写的不是很好,以后还得多多熟悉rxjava这个东西,多用它的操作符,将这个过程写的更加顺滑。

tttt

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值