最近在做一个前端收银的项目,其中一块很恶心的问题就是商品库数据同步的问题,来记录一下整个过程。
由于商品同步的数据量较大,所以采用了文件同步的方式。
整个逻辑流程如下:
①下载服务端数据(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这个东西,多用它的操作符,将这个过程写的更加顺滑。
tt